Kotlin学习之设计模式篇

发表于 3年以前  | 总阅读数:234 次

Android开发的小伙伴对设计模式肯定都不陌生,从Android源码到一些知名开源框架,设计模式无处不在。大家对java语言版本的设计模式基本上还是比较熟悉的,或多或少的都用过。Kotlin语言作为后起之秀,简洁,高效一直是它的标签。本文将带大家了解在kotlin环境下,一些常用设计模式的代码实现。同时还会通过一些设计模式的代码,讲解一些kotlin语言特性。让大家在温习设计模式的同时,能对kotlin语言有进一步的了解。

一、单例模式

讲到设计模式,单例模式肯定是运用最多的,单例模式的实现:定义一个使用private构造方法并且用静态字段持有这个类的实例。在实际的开发过程过程中,单例模式的关键在于保证多线程下仍然是单例,常见的做法有:初始化静态成员(饿汉模式),双重检查锁单例(DCL模式),静态内部类和枚举类。

1、饿汉模式

饿汉模式在类初始化的时候就创建了对象,所以不存在线程安全的问题。我们首先看一下饿汉模式在两种语言环境中的对比。

Java版本

class SingletonJ {
    private static SingletonJ mInstance = new SingletonJ();
    private SingletonJ(){

    }

    public static SingletonJ getInstance(){
        return mInstance;
    }

    public void doTest(){
        System.out.println("test singleton java");
    }
}

kotlin版本

object SingletonK {

    fun doTest(){
        println("test singleton kotlin")
    }
}

kotlin中object是天生的单例,在声明类的同时创建了该类的实例。我们这里反编译一下kotlin的字节码,看一下反编译后的java版本

public final class SingletonK {
   public static final SingletonK INSTANCE;

   public final void doTest() {
      String var1 = "test singleton kotlin";
      boolean var2 = false;
      System.out.println(var1);
   }

   private SingletonK() {
   }

   static {
      SingletonK var0 = new SingletonK();
      INSTANCE = var0;
   }
}

反编译后,和java版本基本的单例基本是一致的。通过上面的反编译代码也可以看出,如果在java代码中调用kotlin单例应该是如下的方式:

SingletonK.INSTANCE.doTest();

饿汉模式有自己的使用局限:

1、如果构造方法中有耗时操作的话,会导致这个类的加载比较慢。

2、饿汉式一开始就创建实例,但是并没有调用,会造成资源浪费。

在kotlin的饿汉模式中还有一个问题,不能定义构造方法。object 中不允许 constructor 函数。

为解决上面的问题,就有后面的三种方式。我们这里以DCL模式为例,介绍下kotlin下如何实现懒加载以及线程安全的。

2、**双重检查锁单例(**DCL)

同样,我们这里列出了java版本和kotlin版本

java版本

class SingleDoubleCheckJava {
    private volatile static SingleDoubleCheckJava instance;

    private SingleDoubleCheckJava() {

    }

    public static SingleDoubleCheckJava getInstance() {
        if (instance == null) {
            synchronized (SingleDoubleCheckJava.class) {
                if (instance == null) {
                    instance = new SingleDoubleCheckJava();
                }
            }
        }
        return instance;
    }
}

kotlin版本

class SingletonDubbleCheckKotlin private constructor() {
    companion object {
        val instance: SingletonDubbleCheckKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            SingletonDubbleCheckKotlin() }
    }

    fun doTest(){
        println("test singleton SingletonDubbleCheckKotlin")
    }
}

大家可以看到kotlin版本实现很简洁,我们下面分析下这段代码实现

首先是companion关键字,Kotlin给Java开发者带来最大改变之一就是废弃了static修饰符。与Java不同的是在Kotlin的类中不允许你声明静态成员或方法。相反,你必须向类中添加companion对象来包装这些静态引用.

使用companion修饰的对象,获得了直接通过容器类名称来访问这个对象的方法和属性。上面的单例的使用方式如下:

    SingletonDubbleCheckKotlin.instance.doTest()

DCL代码中还使用到了委托,by关键字是kotlin语法糖之一,通过这个关键字实现了java中的委托模式,节省了大量的样板代码。这里用到的其实是属性委托。我们这里大概介绍一下属性委托委托

属性语法是:val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是委托类的实现, 因为属性对应的 get()和 set()会被委托给它的 getValue() 和 setValue() 方法。属性的委托不必实现任何的接口,但是需要提供getValue() 和 setValue()函数。

在本例中,属性instance通过lazy()函数返回Lazy对象作为委托对象,实现延迟初始化,下面分析下lazy函数的源码

public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

函数有2个参数,一个是线程安全类型 ,另一个是外部初始化函数,返回值是Lazy类型 。例子中我们传入的参数是SYNCHRONIZED,我们先看一下这个分支的实现。

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

SynchronizedLazyImpl实现了Lazy接口,我们看一下Lazy接口

/**
 * Represents a value with lazy initialization.
 *
 * To create an instance of [Lazy] use the [lazy] function.
 */
public interface Lazy<out T> {
    /**
     * Gets the lazily initialized value of the current Lazy instance.
     * Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
     */
    public val value: T

    /**
     * Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise.
     * Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance.
     */
    public fun isInitialized(): Boolean
}

我们看下属性value的实现,这是个不可变属性,一旦初始化后,就不会在改变。这个属性其实就是我们延时初始化的单例实例。

通过上述代码,我们发现 SynchronizedLazyImpl 覆盖了Lazy接口的value属性,并且重新定义了其属性访问器。其具体逻辑与Java的双重检验是类似的。可以看到value 的get() 方法的实现,当_value !==UNINITIALIZED_VALUE时表示已经初始化了,当_value === UNINITIALIZED_VALUE 则需要初始化,那么就执行了initializer表达式,本例中,就会执行单例类的构造函数SingletonDubbleCheckKotlin()并赋值给_value。

我们在介绍by关键字时提到,lazy需要实现一个getvalue函数返回当前属性的实例。读者读到这里发现我们目前只是实例化了SynchronizedLazyImpl对象,上述代码中并没有发现getvalue函数,继续翻源码我们发现

@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

上面的代码使用到了kotlin的另外一个特性:扩展函数。扩展函数可以在不使用继承或者装饰器模式的情况下,在不改变类本身行为的情况下,为现有类添加新的函数或者属性。上面的代码就是为类Lazy添加了getValue的扩展函数。

源码中lazy还有另外一种重载方式

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

所以默认情况下,对于 lazy 属性的求值是线程安全的,该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。这样可以多个线程同时初始化,最先初始化完成的实例会作为属性最终的值保存下来被使用。而如果你确定初始化将总是发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证以及相关的开销。

至此DCL单例模式的kotlin实现方式分析完毕。后面静态内部类方式和枚举类方式实现起来和java方式区别不大,感兴趣的读者可以自行实现下,这里不再赘述。

小结:

kotlin中object是天生的单例,companion object(伴生对象)为类提供了类似静态方法的访问方式,但是和静态方法还是有区别的,我们在后面的工厂模式中会继续讨论companion。我们这里初步接触到了委托,学习了属性委托的原理和一个延迟委托函数lazy. 在后面的篇幅中还会继续学习委托的其他使用方式。下面我们介绍另外一种常用的创建型模式:工厂模式。

二、工厂模式

1、静态工厂方法(SFM)

静态工厂方法也是我们实际开发中使用频率较高的一种创建型设计模式。

静态工厂方法替代构造方法创建类的实例有以下的优点。

1、有方法名,方法名就表明了一个对象是怎么创建以及它的参数列表是什么。同时方法名可以用来区分相同参数类型的构造函数。

2、可以缓存类的实例,如果实例已经被创建的情况下,可以返回已经创建好的实例。

3、可以返回创建类型的任何子类型,构造函数则不能创建子类型。可以根据使用场景提供灵活的对象创建方式。静态工厂方法顾名思义是需要使用静态方法的,kotlin不支持static关键字。如何实现静态工厂方法呢,上面单例模式中我们提到了伴生对象。伴生对象天生是为静态工厂方法准备的。下面的例子是实现了一个水果工厂,水果工厂生产的不同水果有不同的价格。下面看一下2个版本的代码实现

先看java版本

//Fruit.java
interface Fruit {
    void showPrice();
}
//AbsFruit.java
abstract  class AbsFruit implements Fruit {
    public float mPrice;
}
//Apple.java
class Apple extends AbsFruit {
    Apple(float price){
        mPrice = price;
    }
    @Override
    public void showPrice() {
        System.out.println("  apple price is " + mPrice);
    }
}
//Banana.java
class Banana extends AbsFruit {
    Banana(float price){
        mPrice = price;
    }
    @Override
    public void showPrice() {
        System.out.println(" banana price is " + mPrice);
    }
}
//FruitFactory.java
class FruitFactory {

    public static Fruit getApple(){
        return new Apple(5.0f);
    }

    public static Fruit getBanana(){
        return new Banana(8.0f);
    }

    public static void main(String[] args) {
        Fruit apple = FruitFactory.getApple();
        apple.showPrice();
    }
}

kotlin版本

interface FruitInKotlin{
    val price : Float
    fun showPrice()
}

class FruitFactory{
    companion object{
        fun getApple() : FruitInKotlin = AppleInKotlin(5.0f)

        fun getBanana() : FruitInKotlin = BananaInKotlin(8.0f)
    }
}

class AppleInKotlin (override val price: Float) : FruitInKotlin{
    override fun showPrice() {
        println("  apple in kotlin price is $price")
    }

}

class BananaInKotlin (override val price: Float) : FruitInKotlin{
    override fun showPrice() {
        println("  banana in kotlin $price")
    }
}

fun main() {
    val apple = FruitInKotlin.getApple()
    apple.showPrice()
}

SFM方式适用于产品种类较少的场景,如果种类比较多。需要频繁修改工厂类的代码,不符合开闭原则。面对较多的产品的场景,我们采用工厂方法的模式

2、工厂方法

工厂方法模式给每个产品都配一个专门工厂生产,当需要扩大产品种类时,创建对应新产品的工厂就可以。对已存在产品种类不会造成影响。下面看一下kotlin版本的实现。

interface AbsFruitFactory{
     fun getFruit() : FruitInKotlin
}

class AppleFactory : AbsFruitFactory{
    override fun getFruit(): FruitInKotlin = AppleInKotlin(5.0f)
}

fun main() {
    val appleFactory = AppleFactory()
    val fruit = appleFactory.getFruit()
}

上面的方案虽然也是实现了工厂方法,其实和java的方式没什么区别。我们前面介绍了扩展函数,可以在不改变类结构的情况下,为现有类增加新的方法。在静态工厂方法模式下,我用扩展函数的方式增加工厂可创建的产品种类。这样既不会大量的增加工厂种类,同时也不会频繁修改静态工厂方法类。所以我们就有了下面的代码

fun FruitFactory.Companion.getOrange() : FruitInKotlin = OrangeInKotlin(3.0f)

fun main() {
    val apple = FruitFactory.getApple()
    apple.showPrice()

    val orange = FruitFactory.getOrange()
    orange.showPrice()
}

小结:

通过工厂模式我们学习到,kotlin中没有静态方法,如果你必须使用静态方法,可以在class中使用Companion 对象包装静态引用或方法的定义。这样你就获得了通过类名直接访问这些方法的能力。这种方式看起来和直接定义static 方法区别不大,但其实有本质的不同。companion本质上还是一个对象,是一种特殊的单例模式。companion对象不仅可以有父类,甚至可以实现接口,有自己的名字,同时也可以为companion对象提供扩展方法。工厂模式先介绍到这里,下面我们介绍另外一种创建型模式:

三、builder模式

builder模式也是一种常用的设计模式,常用于复杂对象的构建,例如Android中的AlertDialog. 下面我们介绍两种在kotlin中实现builder模式的方式

1.可变参数

kotlin中的可变参数是一个简短易用的功能,可以让你无需模板代码就可以实现函数的重载。在构造方法中采用默认参数,可以根据需求动态的配置需要的参数构建对象。看下面的例子

class Car(val color : String = "black", val factory : String = "Audi")

fun main() {
    val redCar = Car(color="red"); //不关心汽车品牌,只关心颜色
    val bmwCar = Car(factory = "BMW")//不关心颜色,只关心品牌
}

默认参数方式可以动态配置对象的属性,但是如果一个对象需要设置的属性比较多,都放在构造函数中不太好,这种方式就不太适用。我们看一下下面的方式,

2. apply方法

apply函数是kotlin标准库的函数。我们先看看使用apply是如何构建对象的

class Car(){
    var color : String = "black"
    var factory : String = "Audi"
}
fun main() {
    val newCar = Car().apply {
        factory = "BMW"
        color = "red"
    }
}

看一下apply函数的实现

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

从源码中可以看出,apply函数其实是类型T的扩展函数。参数是一个带接受者的lambda表达式,执行完传入的block后,会把当前实例返回。kotlin中,可以在任何对象上使用apply函数,不需要额外的支持。apply函数的一种使用方式就是创建一个对象实例并且按照正确的方式初始化它的一些属性。和java当中的builder模式是异曲同工的效果,但是使用起来要简洁很多.

小结:

可变参数是kotlin开发中的一个利器,它帮助我们减少了大量处理方法重载所需要的模板代码,并允许为参数设置默认值,相同类型的参数,可以通过参数名进行区分。下面我们介绍另一种创建型模式:原型模式

四、原型模式

原型模式是用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。在 Java 中,原型模式一般可以用 Cloneable 接口和 Object.clone() 来实现。Kotlin 用数据类(data class)提供了解决方案。使用java的时候,当我们需要一个数据容器,需要重新实现toString,equals和hashCode方法。通常IDE都能自动生成这些方法。Kotin中,当使用数据类的时候,我们将免费得到 equals、hashCode、toString 和 copy 这几个函数。通过 copy,我们可以复制一整个对象并且修改所得到的新对象的一些属性。在kotlin中执行数据拷贝的代码看起来是下面这个样子

data class Document(val text: String, val imageList : Array<String>)
fun main() {
    val imageList = arrayOf("1.png","2.png","3.png")
    val document1 = Document("aaaaaa",imageList);
    val document2 = document1.copy(text = "bbbbb")
}

介绍完创建型的设计模式,下面我们介绍一种结构型的设计模式:装饰器模式。

五、装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式采用组合的方式扩展类的功能,深谙设计模式的宗旨。在java模式下,会创建一个新类,实现与原始类一样的接口并将原始类作为一个类成员保存,与原始类同样的行为方法不用修改,直接转发原始类的实例。一些需要修改的方法,在新类中做修改。这种方式的一个缺点就是会产生大量的样板代码。这里介绍下kotlin下实现装饰模式的两种方式。

1、扩展函数

在单例模式中,我们介绍过扩展函数。这里我们直接上代码

interface Shape {
    fun draw()
}

class Circle : Shape{
    override fun draw() {
        println("draw Circle")
    }

}

fun Shape.redColor(decorator : Shape.() -> Unit) {
    println("with red color extend")
    decorator()
}

fun Shape.boldLine(decorator : Shape.() -> Unit) {
    println("with bold line extend")
    decorator()
}

fun main() {
    Circle().run {
        boldLine {
            redColor {
                draw()
            }
        }
    }
}

采用扩展函数的方式实现装饰者模式,没有中间的装饰类,代码简洁。但是只能装饰一个方法,对于多个方法都需要装饰的情况下,可能使用委托的方式更为合适。

2、类委托 使用 by 关键字

使用by 关键字可以将一个接口的实现委托到实现了同样接口的另一个对象。没有任何样板代码的产生. 下面是是用委托的方式实现装饰者模式

interface Shape {
    fun draw()
    fun prepare()
    fun release()
}
class Circle : Shape{
    override fun draw() {
        println("draw Circle")
    }
    override fun prepare() {
        println(" prepare Circle")

    }
    override fun release() {
        println(" release Circle")

    }
}
class RedShapeKotlin(val shape : Shape) : Shape by shape{
    override fun draw() {
        println("with red color by ")
        shape.draw()
    }
}

class BoldShapeKotlin(val shape : Shape) : Shape by shape{
    override fun draw() {
        println("with bold line by")
        shape.draw()
    }
}

fun main() {
    val circle = Circle()
    val decoratorShape = BoldShapeKotlin(RedShapeKotlin(shape = circle))
    decoratorShape.draw();
}

小结:

kotin将委托做为了语言级别的功能做了头等支持,委托是替代继承的一个很好的方法,如果多个地方需要用到相同的代码,这时就可以考虑使用委托。

六、策略模式

策略模式通常是把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。简单理解,策略模式就是对一个算法的不同实现。kotlin中可以使用高阶函数代替不同算法。我们看下面的例子,某种商品根据不同的场景有不同的打折策略,比如新用户5折,还有就是满200减100.我们看一下代码实现

fun fullDisCount( money : Int) : Int{ if(money > 200) return money-100 else return money }
fun NewerDisCount(money : Int) : Int{ return money/2 }

class Customer(val discount : (Int)->Int){
    fun caculate( money : Int) : Int{
        return discount(money)
    }
}

fun main() {
    val newCustomer = Customer(::NewerDisCount)
    newCustomer.caculate(1000);

    val fullDiscountCustomer = Customer(::fullDisCount)
    fullDiscountCustomer.caculate(300)
}

七、模板方法

模板方法指导思想:定义一个算法中的操作框架,而将一些步骤延迟到子类中,使得子类在不改变算法框架结构即可重新定义该算法的某些特定步骤。这个模式与上面的策略模式有类似的效果。都是把不同算法实现延迟到子类中实现。与策略模式不同的是,模板行为算法有更清晰的大纲结构,完全相同的步骤会在抽象类中实现,个性化实现会在子类中实现。下面的例子展示了在线购物通过模板方法的模式支持不同的支付方式。这里和上面的策略模式类似,减少了子类的创建。

class OnlineShopping{
fun submitOrder(pay : ()-&gt;Unit){
    caculatePrice()
    pay()
    sendHome()
}

private fun sendHome(){
    println("send home!")
}

private fun caculatePrice(){
    println("caculate price!")
}

}

fun weixinPay(){ println("pay by weixin") }
fun zfbPay(){println("pay by ZFB")}


fun main() {
var shopping = OnlineShopping()
shopping.submitOrder { weixinPay() }
shopping.submitOrder { zfbPay() }
}

小结:

策略模式和模板方法模式都是通过高阶函数的替代继承的方式,减少了子类的创建。极大的精简了我们的代码结构。这也是我们在学习kotlin的时候,需要注意的地方。函数是kotlin中的一等公民,函数本身也具有自己的类型 。函数类型和数据类型一样,既可用于定义变量,也可用作函数的形参类 型,还可作为函数的返回值类型。

八、观察者模式

观察这模式是应用比较多的一种设计模式,尤其在响应式编程中。

一些知名框架EventBus, RxJava等,都是基于观察者模式设计的。Android Jetpack中的LiveData也是采用的观察者模式,可见这种模式应用的很广泛。看下面这个例子,用户订阅了某些视频号后,这个视频号一旦有更新,需要通知所有的订阅用户。我们下面还是通过对比下java和kotin不同的源码实现,来学习下kotlin下观察者模式的实现。

Java版本

class VideoObserverable extends Observable {
    private List<User> observers = new ArrayList<>();

    void addOberver(User user){
        this.observers.add(user);
    }

    void deleteObserver(User user){
        this.observers.remove(user);
    }

    void notifyUsers (String vid){
        for(User user : observers){
            user.update(this,user.name + "_" + vid);
        }
    }
}

class User implements Observer {
    String name;
    User(String name){
        this.name = name;
    }

    @Override
    public void update(Observable observable, Object o) {
        System.out.println(o);
    }
}

public static void main(String[] args) {
    VideoObserverable videoUpdates = new VideoObserverable();
    videoUpdates.addOberver(new User("Allen"));
    videoUpdates.addOberver(new User("Bob"));
    videoUpdates.notifyUsers("101");
    videoUpdates.notifyUsers("102");
}

kotin版本

interface VideoUpdateListener {
    fun update(message:String)
}

class VideoObserverableInKotlin {
    var observers: MutableList<UserInKotlin> = ArrayList()
    var vid: Int by Delegates.observable(0) { _, old, new ->
        observers.forEach{
            it.update("${it.name}_$vid")
            if (old==new) it.update(" no new value")
            else it.update("${it.name}_$vid")
        }
    }
}

class UserInKotlin(val name: String) : VideoUpdateListener {
    override fun update(message:String) {
        println(message)
    }
}

fun main(args: Array<String>) {
    val videoUpdates = VideoObserverableInKotlin()
    videoUpdates.observers.add(UserInKotlin("Allen"))
    videoUpdates.observers.add(UserInKotlin("Bob"))
    videoUpdates.vid=101
    videoUpdates.vid=102
}

分析上面的代码,主要也是通过委托属性的方式实现了对属性值改变的监听。这里用到了一个kotlin标准函数Delegates.observable ,该函数有两个参数,接受一个初始值,和一个属性改变后的回调函数,回调函数有三个参数,第一个是被赋值的属性,第二个是旧值,第三个是新值。开发者可以根据自己的需求,实现属性改变后的逻辑。我们看一下Delegates的源码

   /**
     * Returns a property delegate for a read/write property that calls a specified callback function when changed.
     * @param initialValue the initial value of the property.
     * @param onChange the callback which is called after the change of the property is made. The value of the property
     *  has already been changed when this callback is invoked.
     *
     *  @sample samples.properties.Delegates.observableDelegate
     */
    public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }

我们上面介绍过属性委托,被委托对象需要实现getValuesetValue方法。上面的observable函数返回一个匿名对象ObservableProperty。我们看下它的源码实现

/**
 * Implements the core logic of a property delegate for a read/write property that calls callback functions when changed.
 * @param initialValue the initial value of the property.
 */
public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
    private var value = initialValue

    /**
     *  The callback which is called before a change to the property value is attempted.
     *  The value of the property hasn't been changed yet, when this callback is invoked.
     *  If the callback returns `true` the value of the property is being set to the new value,
     *  and if the callback returns `false` the new value is discarded and the property remains its old value.
     */
    protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true

    /**
     * The callback which is called after the change of the property is made. The value of the property
     * has already been changed when this callback is invoked.
     */
    protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
}

ObservableProperty对象中我们看到了 getValue 和 setValue 方法。在 setValue方法中,最后会执行 afterChange 方法,在匿名对象中重写了 afterChange 方法,并且会调用 observable 方法传进来的 onChange 参数,这样每次属性改变,就会回调 onChange 方法,从而达到监听的目的。

小结:

这里又是用到了属性委托,Delegates.observable是kotlin中的委托函数, 在学习单例模式的时候,我们学习过lazy函数,它也是kotlin中的委托函数。加上上面提到的类委托。kotlin中的委托的使用方式,我们基本都了解过了。在日常的开发过程中运用好委托,可以大量减少样板代码的编写。从而提高我们的开发速度。

总结

通过上面的学习,我们了解到了kotlin中单例的实现, 静态方法的实现,了解到了扩展和委托的运用方式,了解到了使用可变参数和数据类可以大幅减少样板代码的产生,学习了高阶函数的使用方式和场景。同时也学习了一些kotlin中标准的库函数的原理和使用场景。

希望通过学习这些模式,加深读者们对kotlin语言的理解。从而在日后的开发过程中,提高大家的开发效率和代码质量。最后,希望这篇文章能给你些启发,让你认识到 Kotlin 可以为广为人知的问题带来的新的解决方案。

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/OLMm8KbQMgc0eSnqvVtS1w

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录