美文网首页
kotlin学习之设计模式篇

kotlin学习之设计模式篇

作者: allenliushaohua | 来源:发表于2021-03-31 17:54 被阅读0次

    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<T>对象作为委托对象,实现延迟初始化,下面分析下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<T>类型 。例子中我们传入的参数是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<T>接口,我们看一下Lazy<T>接口

    /**
     * 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<T> 需要实现一个getvalue函数返回当前属性的实例。
    读者读到这里发现我们目前只是实例化了SynchronizedLazyImpl对象,上述代码中并没有发现getvalue函数,继续翻源码我们发现

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

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

    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")//不关心颜色,只关心品牌
    }
    

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

    1. 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,equalshashCode方法。通常IDE都能自动生成这些方法。
    Kotin中,当使用数据类的时候,我们将免费得到 equalshashCodetoStringcopy 这几个函数。通过 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 : ()->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 可以为广为人知的问题带来的新的解决方案

    参考文献:
    https://kotlinlang.org/docs/
    https://www.runoob.com/design-pattern/design-pattern-tutorial.html
    http://ddrv.cn/a/13290

    相关文章

      网友评论

          本文标题:kotlin学习之设计模式篇

          本文链接:https://www.haomeiwen.com/subject/xlyyqltx.html