Kotlin的裝飾者模式與原始碼擴充套件

天星技術團隊發表於2019-01-16

作者 點先生 日期 2018.8.26

閒聊

最近一直不在狀態,月初就被部落格質量的事給弄的情緒低落,之後群裡又走了兩個朋友,心情是一直在低谷徘徊,部落格也是不想寫,狀態一天不如一天,總之就是一句話,不想工作。所以……
有沒有小(fu)姐(luo)姐(li)私聊我啊!

設計模式剛入門的小夥伴可以先看看這篇《設計模式入門》,在文章末尾也將列出“設計模式系列”文章。歡迎大家關注留言投幣丟香蕉。天星技術團QQ:557247785。

什麼是裝飾者模式

為了方便理解,我們先舉一些例子。
人是一個類,要上街的話,人就得穿衣服,褲子,鞋子,這是最簡單的裝扮,有些人還會打領帶、揹包、戴帽子等等。在這個例子中,“人”是一個被裝飾者衣服褲子鞋子是裝飾者。在整個打扮過程中,被裝飾者類是不會被更改的,產生變化的是裝飾者類的數量。
在吃火鍋之前,我們會調料碗。那麼空碗就是被裝飾者,油、香菜、蔥花就是裝飾者;
點菜這一步驟,也是裝飾者模式,鴛鴦鍋就是被裝飾者,麻辣牛肉、毛肚、鴨腸、菌肝、千層肚、鴨血、紅糖餈粑就是裝飾者;
裝飾模式就是在不改變原類檔案和使用繼承的情況下,動態地擴充套件一個物件的功能。
聊到這裡,有沒有一點餓?

Kotlin的裝飾者模式與原始碼擴充套件

現在呢?

走進裝飾者模式

首先看一下裝飾者模式的UML圖

Kotlin的裝飾者模式與原始碼擴充套件

可以看出裝飾模式中,有四個參與者:

  1. Component(抽象元件): 定義物件的介面抽象類,以規範準備接收附加責任的物件。
  2. ConcreteComponent(具體元件): 具體的物件,抽象裝飾者能給他新增職責
  3. Decorator(抽象裝飾者): 持有一個抽象元件物件的例項,並定義一個與抽象元件一致的介面。
  4. ConcreteDecorator(具體裝飾者): 具體的裝飾物件。給內部持有的具體元件增加具體的職責;

我是按括號裡的文字來記憶的,會比較容易記住。剛剛我們舉例寫到的“人”就是抽象元件;“男人女人”就是具體元件;“裝飾品”就是抽象元件;“帽子”就是具體元件;

裝飾模式的特點

  1. 裝飾者和被裝飾者有相同的超型別
  2. 可以用一個或者多個裝飾者包裝一個物件
  3. 任何需要被裝飾者物件的場合,可以用裝飾過的物件代替它。(其實就是因為特點一)
  4. 裝飾者可以在所委託被裝飾者的行為之前與/或之後,加上自己的行為。(很重要)
  5. 物件可以在任何時候被裝飾,包括執行時。

裝飾模式的使用場合

  1. 在不影響其他物件的情況下,以動態、透明的方式給單個物件增加/撤銷職責。
  2. 當不能採用生成子類的方法進行擴充時。

活生生的例子

我們先來分析一下上面提到的人化妝的例子。首先來建立四個參與者。
1.抽象元件Human ,給他一個自我介紹的描述,再新增一個方法,說出自己穿了什麼。

abstract class Human {
    open var description = "I`m a human."
    abstract fun getDress() : String
}
複製程式碼
  1. 繼承抽象元件的具體元件MaleFemale,改變下自我描述。
class Male : Human() {
    override var description: String
        get() = "我是男性"
        set(value) {}

    override fun getDress(): String {
        return "我穿了內褲"
    }
}
複製程式碼
  1. 繼承抽象元件的抽象裝飾者Decoration
abstract class Decoration : Human() {
    abstract override var description: String
}
複製程式碼
  1. 繼承抽象裝飾者的具體裝飾者,這裡我寫一個帽子類,其他的隨意新增。
class Hats : Decoration() {
    override var description: String
        get() = " 我有帽子"
        set(value) {}

    override fun getDress(): String {
        return "帽子"
    }
}
複製程式碼

現在有了四個參與者,結構也按照UML寫好了。那接下來就是包裝了。
穿衣服時,我們會一件一件的穿(沒有誰會同時穿吧?),所以,我們穿了褲子後,再穿衣服時,被裝飾者是“男女人+褲子”。看圖!

Kotlin的裝飾者模式與原始碼擴充套件

裝飾者是一個一個去包裹被裝飾者,這裡要注意,衣服和褲子跟男人是同一個超類,我們在包裹的時候,需要把被包裝的物件(被包裝的物件可能是具體元件,也可能是已經被裝飾者裝飾之後的具體元件),傳到裝飾者中,所以我們需要在具體裝飾者類中新增一個超類引數。這樣才能得到被包裝物件的所有引數。所以剛剛的具體裝飾者類還沒寫完,補充完整應該是:

class Hats(var human: Human) : Decoration() {
    override var description: String
        get() = "帽子"
        set(value) {}

    override fun getDress(): String {
        return human.getDress() + " 帽子"
    }
}
複製程式碼

再創造幾個具體裝飾者類,此時專案結構就是這樣的。

Kotlin的裝飾者模式與原始碼擴充套件

現在我們創造一個超人,給她穿上帽子,斗篷,並在介面中顯示一下自己穿了些什麼。

class MainActivity : AppCompatActivity() {
    var superMan : Human? = Female()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        superMan = Cloak(Hats(this.superMan!!))
        tv_textview.text = superMan?.description + superMan?.getDress()
    }
}
複製程式碼

結果如下:

Kotlin的裝飾者模式與原始碼擴充套件

總結一下

通過例子應該對裝飾模式有個初步的理解了。再寫demo的時候也只需要記住一下幾步:

  1. 按照裝飾模式UML圖,寫出四個參與者類。
  2. 在具體裝飾者類的主建構函式中新增超類引數
  3. 父類引用指向子類物件創造具體元件
  4. 用具體裝飾者裝飾物件(需要什麼裝飾什麼)

裝飾模式優點: 裝飾模式比普通的繼承更加靈活,能夠在執行時更改元件的功能。而繼承的類在執行前就決定了所有功能。
缺點: 會創造很多的小類。別人看程式碼的時候會很腦殼痛。
注意: 裝飾模式中,裝飾的順序很重要。先穿“褲子”再穿“衣服”跟先穿“衣服”後穿“褲子”是不一樣的

原始碼中的裝飾模式Java I/O

我當初決定要學習設計模式的初衷,是為了看原始碼。現在我們就來一起來看看原始碼, 我也會順便把我在這當中學到的一些看原始碼的方式方法說出來。大佬們請忽略這句話。
檢視原始碼技巧一:檢視它的父類子類並畫圖。
在AS右上角有個hierarchy按鈕,點它可以檢視當前類的直接父類和全部子類。

Kotlin的裝飾者模式與原始碼擴充套件

java I/O 是比較龐大的一個庫,如果直接看其程式碼,很難知道每個類都在幹啥,也不知道它究竟怎麼運作的。實話說,我以前就看暈了。
通過看它的類結構。我們能畫出這樣一張圖:

Kotlin的裝飾者模式與原始碼擴充套件

在這個設計中,InputStream就是抽象元件,FilterInputStream就是抽象裝飾者, StringBufferInputStream、ByteArrayInputStream等是具體元件,LineNumberInputStream、DataInputStream、BufferedInputStream等是具體裝飾者。
幾個具體元件提供了不同型別的基本位元組讀取功能。
具體裝飾類提供了額外的功能。例如:BufferedInputStream提供readline()方法。

原始碼擴充套件

接下來我們試試自編寫一個新的具體裝飾者類。

//將所有大寫字元轉為小寫
class LowerCaseInputSteam(inputStream: InputStream) : FilterInputStream(inputStream){
    override fun read(): Int {
        val result = super.read()
        if(result==-1) return result
        else return Character.toLowerCase(result)
    }
    
    override fun read(b: ByteArray, off: Int, len: Int): Int {
        val  result = super.read(b, off, len)
        for (i in off until off+result){
            b[i] = Character.toLowerCase(b[i].toInt()).toByte()
        }
        return result
    }
}
複製程式碼

此處需要實現兩個方法,一個針對位元組,一個針對位元組組。

        try {
            var inputStream : InputStream =
                    LowerCaseInputSteam(BufferedInputStream(FileInputStream("手機檔案路徑")))
            c = inputStream.read()
            while (c!! >0) {
                stringBuffer?.append(c!!)
                c = inputStream.read()
            }
            tv_textview.text = stringBuffer.toString()
        }catch (e : IOException){
            e.printStackTrace()
        }
複製程式碼

第一次寫關於原始碼的東西,寫的不好的地方,多多提意見。
下一次我將寫代理模式,也會講到裝飾模式和代理模式的區別,和本章未提到的裝飾模式的透明性。

以下是我“設計模式系列”文章,歡迎大家關注留言投幣丟香蕉。

設計模式入門
Java與Kotlin的單例模式
Kotlin的裝飾者模式與原始碼擴充套件
由淺到深瞭解工廠模式

相關文章