kotlin學習之設計模式篇

Android開發(fā)的小伙伴對設計模式肯定都不陌生,從Android源碼到一些知名開源框架资铡,設計模式無處不在。大家對java語言版本的設計模式基本上還是比較熟悉的,或多或少的都用過均蜜。Kotlin語言作為后起之秀,簡潔芒率,高效一直是它的標簽囤耳。本文將帶大家了解在kotlin環(huán)境下,一些常用設計模式的代碼實現(xiàn)偶芍。同時還會通過一些設計模式的代碼充择,講解一些kotlin語言特性。讓大家在溫習設計模式的同時匪蟀,能對kotlin語言有進一步的了解椎麦。

單例模式

講到設計模式,單例模式肯定是運用最多的材彪,單例模式的實現(xiàn):定義一個使用private構造方法并且用靜態(tài)字段持有這個類的實例观挎。在實際的開發(fā)過程過程中,單例模式的關鍵在于保證多線程下仍然是單例段化,常見的做法有:初始化靜態(tài)成員(餓漢模式)嘁捷,雙重鎖檢查單例(DCL模式),靜態(tài)內部類和枚舉類显熏。

1雄嚣、餓漢模式

餓漢模式在類初始化的時候就創(chuàng)建了對象,所以不存在線程安全的問題喘蟆。我們首先看一下餓漢模式在兩種語言環(huán)境中的對比缓升。
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是天生的單例夷磕,在聲明類的同時創(chuàng)建了該類的實例。我們這里反編譯一下kotlin的字節(jié)碼仔沿,看一下反編譯后的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碾局、餓漢式一開始就創(chuàng)建實例,但是并沒有調用奴艾,會造成資源浪費净当。
在kotlin的餓漢模式中還有一個問題,不能定義構造方法蕴潦。object 中不允許 constructor 函數(shù)像啼。
為解決上面的問題,就有后面的三種方式潭苞。我們這里以DCL模式為例忽冻,介紹下kotlin下如何實現(xiàn)懶加載以及線程安全的。

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版本實現(xiàn)很簡潔,我們下面分析下這段代碼實現(xiàn)
首先是companion關鍵字蝗碎,Kotlin給Java開發(fā)者帶來最大改變之一就是廢棄了static修飾符湖笨。與Java不同的是在Kotlin的類中不允許你聲明靜態(tài)成員或方法。相反蹦骑,你必須向類中添加companion對象來包裝這些靜態(tài)引用.
使用companion修飾的對象慈省,獲得了直接通過容器類名稱來訪問這個對象的方法和屬性。上面的使用單例的使用方式如下:

    SingletonDubbleCheckKotlin.instance.doTest()

DCL代碼中還使用到了委托脊串,by關鍵字是kotlin語法糖之一辫呻,通過這個關鍵字實現(xiàn)了java中的委托模式清钥,節(jié)省了大量的樣板代碼琼锋。這里用到的其實是屬性委托。我們這里大概介紹一下屬性委托
委托屬性語法是: val/var <屬性名>: <類型> by <表達式>祟昭。在 by 后面的表達式是委托類的實現(xiàn)缕坎, 因為屬性對應的 get()和 set()會被委托給它的 getValue() 和 setValue() 方法。 屬性的委托不必實現(xiàn)任何的接口篡悟,但是需要提供getValue() 和 setValue()函數(shù)谜叹。

在本例中匾寝,屬性instance通過lazy()函數(shù)返回Lazy<T>對象作為委托對象,實現(xiàn)延遲初始化荷腊,下面分析下lazy函數(shù)的源碼

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)
    }

函數(shù)有2個參數(shù)艳悔,一個是線程安全類型 ,另一個是外部初始化函數(shù)女仰,返回值是Lazy<T>類型 猜年。例子中我們傳入的參數(shù)是SYNCHRONIZED,我們先看一下這個分支的實現(xiàn)疾忍。

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實現(xiàn)了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的實現(xiàn),這是個不可變屬性一罩,一旦初始化后杨幼,就不會在改變。這個屬性其實就是我們延時初始化的單例實例聂渊。
通過上述代碼差购,我們發(fā)現(xiàn) SynchronizedLazyImpl 覆蓋了Lazy接口的value屬性,并且重新定義了其屬性訪問器汉嗽。其具體邏輯與Java的雙重檢驗是類似的歹撒。可以看到value 的get() 方法的實現(xiàn)诊胞,當_value !==UNINITIALIZED_VALUE
時 表示已經初始化了暖夭,當_value === UNINITIALIZED_VALUE 則需要初始化,那么就執(zhí)行了initializer表達式撵孤,本例中迈着,就會執(zhí)行單例類的構造函數(shù)SingletonDubbleCheckKotlin()并賦值給_value。
我們在介紹by關鍵字時提到邪码,lazy<T> 需要實現(xiàn)一個getvalue函數(shù)返回當前屬性的實例裕菠。
讀者讀到這里發(fā)現(xiàn)我們目前只是實例化了SynchronizedLazyImpl對象,上述代碼中并沒有發(fā)現(xiàn)getvalue函數(shù)闭专,繼續(xù)翻源碼我們發(fā)現(xiàn)

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

上面的代碼使用到了kotlin的另外一個特性:擴展函數(shù)奴潘。擴展函數(shù)可以在不使用繼承或者裝飾器模式的情況下,在不改變類本身行為的情況下影钉,為現(xiàn)有類添加新的函數(shù)或者屬性画髓。上面的代碼就是為類Lazy<T>添加了getValue的擴展函數(shù)。

源碼中l(wèi)azy還有另外一種重載方式

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

所以默認情況下平委,對于 lazy 屬性的求值是線程安全的奈虾,該值只在一個線程中計算,并且所有線程會看到相同的值。如果初始化委托的同步鎖不是必需的肉微,這樣多個線程可以同時執(zhí)行匾鸥,那么將 LazyThreadSafetyMode.PUBLICATION 作為參數(shù)傳遞給 lazy() 函數(shù)。 這樣可以多個線程同時初始化碉纳,最先初始化完成的實例會作為屬性最終的值保存下來被使用勿负。而如果你確定初始化將總是發(fā)生在單個線程,那么你可以使用 LazyThreadSafetyMode.NONE 模式劳曹, 它不會有任何線程安全的保證以及相關的開銷笆环。

至此DCL單例模式的kotlin實現(xiàn)方式分析完畢。后面靜態(tài)內部類方式和枚舉類方式實現(xiàn)起來和java方式區(qū)別不大厚者,感興趣的讀者可以自行實現(xiàn)下躁劣,這里不再贅述。
小結:
kotlin中object是天生的單例库菲,companion object(伴生對象)為類提供了類似靜態(tài)方法的訪問方式账忘,但是和靜態(tài)方法還是有區(qū)別的,我們在后面的工廠模式中會繼續(xù)討論companion熙宇。我們這里初步接觸到了委托鳖擒,學習了屬性委托的原理和一個延遲委托函數(shù)lazy. 在后面的篇幅中還會繼續(xù)學習委托的其他使用方式。
下面我們介紹另外一種常用的創(chuàng)建型模式:工廠模式烫止。

工廠模式

1蒋荚、靜態(tài)工廠方法(SFM)

靜態(tài)工廠方法也是我們實際開發(fā)中使用頻率較高的一種創(chuàng)建型設計模式。靜態(tài)工廠方法替代構造方法創(chuàng)建類的實例有以下的優(yōu)點馆蠕。
1期升、有方法名,方法名就表明了一個對象是怎么創(chuàng)建以及它的參數(shù)列表是什么互躬。同時方法名可以用來區(qū)分相同參數(shù)類型的構造函數(shù)播赁。
2、可以緩存類的實例吼渡,如果實例已經被創(chuàng)建的情況下容为,可以返回已經創(chuàng)建好的實例。
3寺酪、可以返回創(chuàng)建類型的任何子類型坎背,構造函數(shù)則不能創(chuàng)建子類型〖娜福可以根據(jù)使用場景提供靈活的對象創(chuàng)建方式得滤。
靜態(tài)工廠方法顧名思義是需要使用靜態(tài)方法的,kotlin不支持static關鍵字咙俩。如何實現(xiàn)靜態(tài)工廠方法呢耿戚,上面單例模式中我們提到了伴生對象。伴生對象天生是為靜態(tài)工廠方法準備的阿趁。下面的例子是實現(xiàn)了一個水果工廠膜蛔,水果工廠生產的不同水果有不同的價格。下面看一下2個版本的代碼實現(xiàn)
先看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悍募、工廠方法

工廠方法模式給每個產品都配一個專門工廠生產蘑辑,當需要擴大產品種類時,創(chuàng)建對應新產品的工廠就可以坠宴。對已存在產品種類不會造成影響洋魂。下面看一下kotlin版本的實現(xiàn)。

interface AbsFruitFactory{
     fun getFruit() : FruitInKotlin
}

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

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

上面的方案雖然也是實現(xiàn)了工廠方法喜鼓,其實和java的方式沒什么區(qū)別副砍。我們前面介紹了擴展函數(shù),可以在不改變類結構的情況下庄岖,為現(xiàn)有類增加新的方法豁翎。在靜態(tài)工廠方法模式下,我用擴展函數(shù)的方式增加工廠可創(chuàng)建的產品種類隅忿。這樣既不會大量的增加工廠種類心剥,同時也不會頻繁修改靜態(tài)工廠方法類。所以我們就有了下面的代碼

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

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

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

小結:
通過工廠模式我們學習到背桐,kotlin中沒有靜態(tài)方法刘陶,如果你必須使用靜態(tài)方法,可以在class中使用Companion 對象包裝靜態(tài)引用或方法的定義牢撼。這樣你就獲得了通過類名直接訪問這些方法的能力匙隔。這種方式看起來和直接定義static 方法區(qū)別不大,但其實有本質的不同熏版。companion本質上還是一個對象纷责,是一種特殊的單例模式。companion對象不僅可以有父類撼短,甚至可以實現(xiàn)接口再膳,有自己的名字,同時也可以為companion對象提供擴展方法曲横。工廠模式先介紹到這里喂柒,下面我們介紹另外一種創(chuàng)建型模式:builder模式

builder模式

builder模式也是一種常用的設計模式不瓶,常用于復雜對象的構建,例如Android中的AlertDialog. 下面我們介紹兩種在kotlin中實現(xiàn)builder模式的方式
1.可變參數(shù)
kotlin中的可變參數(shù)是一個簡短易用的功能灾杰,可以讓你無需模板代碼就可以實現(xiàn)函數(shù)的重載蚊丐。在構造方法中采用默認參數(shù),可以根據(jù)需求動態(tài)的配置需要的參數(shù)構建對象艳吠÷蟊福看下面的例子

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

fun main() {
    val redCar = Car(color="red"); //不關心汽車品牌,只關心顏色
    val bmwCar = Car(factory = "BMW")//不關心顏色昭娩,只關心品牌
}

默認參數(shù)方式可以動態(tài)配置對象的屬性凛篙,但是如果一個對象需要設置的屬性比較多,都放在構造函數(shù)中不太好栏渺,這種方式就不太適用呛梆。我們看一下下面的方式,

  1. apply方法
    apply函數(shù)是kotlin標準庫的函數(shù)磕诊。我們先看看使用apply是如何構建對象的
class Car(){
    var color : String = "black"
    var factory : String = "Audi"
}
fun main() {
    val newCar = Car().apply {
        factory = "BMW"
        color = "red"
    }
}

看一下apply函數(shù)的實現(xiàn)

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

從源碼中可以看出削彬,apply函數(shù)其實是類型T的擴展函數(shù)。參數(shù)是一個帶接受者的lambda表達式秀仲,執(zhí)行完傳入的block后融痛,會把當前實例返回。kotlin中神僵,可以在任何對象上使用apply函數(shù)雁刷,不需要額外的支持。apply函數(shù)的一種使用方式就是創(chuàng)建一個對象實例并且按照正確的方式初始化它的一些屬性保礼。和java當中的builder模式是異曲同工的效果沛励,但是使用起來要簡潔很多.
小結:
可變參數(shù)是kotlin開發(fā)中的一個利器,它幫助我們減少了大量處理方法重載所需要的模板代碼炮障,并允許為參數(shù)設置默認值目派,相同類型的參數(shù),可以通過參數(shù)名進行區(qū)分胁赢。
下面我們介紹另一種創(chuàng)建型模式:原型模式

原型模式

原型模式是用原型實例指定創(chuàng)建對象的種類企蹭,并通過拷貝這些原型創(chuàng)建新的對象。在 Java 中智末,原型模式一般可以用 Cloneable 接口和 Object.clone() 來實現(xiàn)谅摄。
Kotlin 用數(shù)據(jù)類(data class)提供了解決方案。使用java的時候系馆,當我們需要一個數(shù)據(jù)容器送漠,需要重新實現(xiàn)toString,equalshashCode方法。通常IDE都能自動生成這些方法由蘑。
Kotin中闽寡,當使用數(shù)據(jù)類的時候代兵,我們將免費得到 equalshashCode爷狈、toStringcopy 這幾個函數(shù)植影。通過 copy,我們可以復制一整個對象并且修改所得到的新對象的一些屬性淆院。
在kotlin中執(zhí)行數(shù)據(jù)拷貝的代碼看起來是下面這個樣子

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")
}

介紹完創(chuàng)建型的設計模式何乎,下面我們介紹一種結構型的設計模式:裝飾器模式句惯。

裝飾器模式

裝飾器模式(Decorator Pattern)允許向一個現(xiàn)有的對象添加新的功能土辩,同時又不改變其結構。
這種模式采用組合的方式擴展類的功能抢野,深諳設計模式的宗旨拷淘。在java模式下,會創(chuàng)建一個新類指孤,實現(xiàn)與原始類一樣的接口并將原始類作為一個類成員保存启涯,與原始類同樣的行為方法不用修改,直接轉發(fā)原始類的實例恃轩。一些需要修改的方法结洼,在新類中做修改。這種方式的一個缺點就是會產生大量的樣板代碼叉跛。這里介紹下kotlin下實現(xiàn)裝飾模式的兩種方式松忍。
1、擴展函數(shù)
在單例模式中筷厘,我們介紹過擴展函數(shù)鸣峭。這里我們直接上代碼

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()
            }
        }
    }
}

采用擴展函數(shù)的方式實現(xiàn)裝飾者模式,沒有中間的裝飾類酥艳,代碼簡潔摊溶。但是只能裝飾一個方法,對于多個方法都需要裝飾的情況下充石,可能使用委托的方式更為合適莫换。
2、類委托 使用 by 關鍵字
使用by 關鍵字可以將一個接口的實現(xiàn)委托到實現(xiàn)了同樣接口的另一個對象骤铃。沒有任何樣板代碼的產生. 下面是是用委托的方式實現(xiàn)裝飾者模式

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將委托做為了語言級別的功能做了頭等支持浓镜,委托是替代繼承的一個很好的方法,如果多個地方需要用到相同的代碼劲厌,這時就可以考慮使用委托膛薛。

策略模式

策略模式通常是把一系列的算法包裝到一系列的策略類里面,作為一個抽象策略類的子類补鼻。簡單理解哄啄,策略模式就是對一個算法的不同實現(xiàn)雅任。kotlin中可以使用高階函數(shù)代替不同算法。我們看下面的例子咨跌,某種商品根據(jù)不同的場景有不同的打折策略沪么,比如新用戶5折,還有就是滿200減100.我們看一下代碼實現(xiàn)

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)
}

模板方法

模板方法指導思想:定義一個算法中的操作框架锌半,而將一些步驟延遲到子類中禽车,使得子類在不改變算法框架結構即可重新定義該算法的某些特定步驟。這個模式與上面的策略模式有類似的效果刊殉。都是把不同算法實現(xiàn)延遲到子類中實現(xiàn)殉摔。與策略模式不同的是,模板行為算法有更清晰的大綱結構记焊,完全相同的步驟會在抽象類中實現(xiàn)逸月,個性化實現(xiàn)會在子類中實現(xiàn)。下面的例子展示了在線購物通過模板方法的模式支持不同的支付方式遍膜。這里和上面的策略模式類似碗硬,減少了子類的創(chuàng)建。

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() }
}

小結:策略模式和模板方法模式都是通過高階函數(shù)的替代繼承的方式恩尾,減少了子類的創(chuàng)建。極大的精簡了我們的代碼結構挽懦。這也是我們在學習kotlin的時候翰意,需要注意的地方。函數(shù)是kotlin中的一等公民巾兆,函數(shù)本身也具有自己的類型 猎物。 函數(shù)類型和數(shù)據(jù)類型一樣,既可用于定義變量角塑,也可用作函數(shù)的形參類 型蔫磨,還可作為函數(shù)的返回值類型。

觀察者模式

觀察這模式是應用比較多的一種設計模式圃伶,尤其在響應式編程中堤如。一些知名框架EventBus, RxJava等,都是基于觀察者模式設計的窒朋。Android Jetpack中的LiveData也是采用的觀察者模式搀罢,可見這種模式應用的很廣泛〗男桑看下面這個例子榔至,用戶訂閱了某些視頻號后,這個視頻號一旦有更新欺劳,需要通知所有的訂閱用戶唧取。我們下面還是通過對比下java和kotin不同的源碼實現(xiàn)铅鲤,來學習下kotlin下觀察者模式的實現(xiàn)。
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
}

分析上面的代碼枫弟,主要也是通過委托屬性的方式實現(xiàn)了對屬性值改變的監(jiān)聽邢享。這里用到了一個kotlin標準函數(shù)Delegates.observable ,該函數(shù)有兩個參數(shù)淡诗,接受一個初始值骇塘,和一個屬性改變后的回調函數(shù),回調函數(shù)有三個參數(shù)韩容,第一個是被賦值的屬性款违,第二個是舊值,第三個是新值宙攻。
開發(fā)者可以根據(jù)自己的需求奠货,實現(xiàn)屬性改變后的邏輯介褥。我們看一下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)
        }


我們上面介紹過屬性委托座掘,被委托對象需要實現(xiàn)getValuesetValue方法。上面的observable函數(shù)返回一個匿名對象ObservableProperty柔滔。我們看下它的源碼實現(xiàn)

/**
 * 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方法中,最后會執(zhí)行 afterChange 方法睛廊,在匿名對象中重寫了 afterChange 方法形真,并且會調用 observable 方法傳進來的 onChange 參數(shù),這樣每次屬性改變超全,就會回調 onChange 方法咆霜,從而達到監(jiān)聽的目的。
小結
這里又是用到了屬性委托嘶朱,Delegates.observable是kotlin中的委托函數(shù)蛾坯, 在學習單例模式的時候,我們學習過lazy函數(shù)疏遏,它也是kotlin中的委托函數(shù)脉课。加上上面提到的類委托。kotlin中的委托的使用方式财异,我們基本都了解過了倘零。在日常的開發(fā)過程中運用好委托,可以大量減少樣板代碼的編寫。從而提高我們的開發(fā)速度旨怠。

總結

通過上面的學習苫耸,我們了解到了kotlin中單例的實現(xiàn), 靜態(tài)方法的實現(xiàn),了解到了擴展和委托的運用方式袖瞻,了解到了使用可變參數(shù)和數(shù)據(jù)類可以大幅減少樣板代碼的產生跌穗,學習了高階函數(shù)的使用方式和場景。同時也學習了一些kotlin中標準的庫函數(shù)的原理和使用場景虏辫。
希望通過學習這些模式蚌吸,加深讀者們對kotlin語言的理解。從而在日后的開發(fā)過程中砌庄,提高大家的開發(fā)效率和代碼質量羹唠。最后,希望這篇文章能給你些啟發(fā)娄昆,讓你認識到 Kotlin 可以為廣為人知的問題帶來的新的解決方案

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末佩微,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子萌焰,更是在濱河造成了極大的恐慌哺眯,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扒俯,死亡現(xiàn)場離奇詭異奶卓,居然都是意外死亡,警方通過查閱死者的電腦和手機撼玄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門夺姑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掌猛,你說我怎么就攤上這事盏浙。” “怎么了荔茬?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵废膘,是天一觀的道長。 經常有香客問我慕蔚,道長丐黄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任坊萝,我火速辦了婚禮孵稽,結果婚禮上,老公的妹妹穿的比我還像新娘十偶。我一直安慰自己菩鲜,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布惦积。 她就那樣靜靜地躺著接校,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛛勉,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天鹿寻,我揣著相機與錄音,去河邊找鬼诽凌。 笑死毡熏,一個胖子當著我的面吹牛,可吹牛的內容都是我干的侣诵。 我是一名探鬼主播痢法,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杜顺!你這毒婦竟也來了财搁?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤躬络,失蹤者是張志新(化名)和其女友劉穎尖奔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體穷当,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡提茁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了膘滨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甘凭。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡稀拐,死狀恐怖火邓,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情德撬,我是刑警寧澤铲咨,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站蜓洪,受9級特大地震影響纤勒,放射性物質發(fā)生泄漏。R本人自食惡果不足惜隆檀,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一摇天、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恐仑,春花似錦泉坐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至歧斟,卻和暖如春纯丸,著一層夾襖步出監(jiān)牢的瞬間偏形,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工觉鼻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留俊扭,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓坠陈,卻偏偏與公主長得像统扳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子畅姊,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內容