Kotlin 語言下設(shè)計模式的不同實(shí)現(xiàn)

偶然在 Github 上看到 dbacinski 寫的 Kotlin 語言下設(shè)計模式的不同實(shí)現(xiàn)(這里的不同是相對于 Java 語言的)咙俩,有些實(shí)現(xiàn)非常好拇颅,但是有些實(shí)現(xiàn)的例子不是很贊同颊艳。所以自己寫了 Kotlin 語言版本的 23 種設(shè)計模式的實(shí)現(xiàn)朵夏,充分利用 Kotlin 的語法糖锌唾,例如單例模式篓像、策略模式等可以很巧妙地實(shí)現(xiàn)嗜逻,其他實(shí)現(xiàn)方式與 Java 不變的也有代碼示例涩僻,就當(dāng)是回顧設(shè)計模式。

1. 創(chuàng)建型模式

1.1 工廠方法模式

工廠方法把創(chuàng)建對象的過程抽象為接口,由工廠的子類決定對象的創(chuàng)建逆日,Kotlin 下的實(shí)現(xiàn)與 Java 一樣嵌巷。

interface Product {
    val name: String
}

class ProductA(override val name: String = "ProductA") : Product
class ProductB(override val name: String = "ProductB") : Product

interface Factory {
    fun makeProduct(): Product
}

class FactoryA : Factory {
    override fun makeProduct() = ProductA()
}
class FactoryB : Factory {
    override fun makeProduct() = ProductB()
}

1.2 抽象工廠模式

工廠方法針對一種產(chǎn)品,而抽象工廠是針對一系列產(chǎn)品室抽,為每種產(chǎn)品定義一個工廠方法搪哪,工廠子類負(fù)責(zé)創(chuàng)建該系列的多種產(chǎn)品,Kotlin 下的實(shí)現(xiàn)與 Java 一樣坪圾。

class SeriesATextView(context: Context?) : TextView(context)
class SeriesBTextView(context: Context?) : TextView(context)

class SeriesAButton(context: Context?) : Button(context)
class SeriesBButton(context: Context?) : Button(context)

interface AbstractFactory {
    val context: Context?
    fun makeTextView(): TextView
    fun makeButton(): Button
}

class SeriesAFactory(override val context: Context?) : AbstractFactory {
    override fun makeTextView() = SeriesATextView(context)
    override fun makeButton() = SeriesAButton(context)
}

class SeriesBFactory(override val context: Context?) : AbstractFactory {
    override fun makeTextView() = SeriesBTextView(context)
    override fun makeButton() = SeriesBButton(context)
}

1.3 建造者模式

建造者模式是為了構(gòu)建復(fù)雜對象的晓折,一般情況下,Kotlin 中使用標(biāo)準(zhǔn)的apply函數(shù)就可以了兽泄,例如下面創(chuàng)建 Dialog 的例子:

val dialog = Dialog(context).apply { 
    setTitle("DialogA") 
    setCancelable(true)
    setCanceledOnTouchOutside(true)
    setContentView(contentView)
}

不過上面的代碼中在 apply 里的 lambda 表達(dá)式里可以調(diào)用 Dialog.show() 等其他與構(gòu)建對象無關(guān)的方法漓概,或者不想公開構(gòu)造函數(shù),只想通過 Builder 來構(gòu)建對象已日,這時可以使用 Type-Safe Builders:

class Car (
    val model: String?,
    val year: Int
) {
    private constructor(builder: Builder) : this(builder.model, builder.year)

    class Builder {
        var model: String? = null
        var year: Int = -1

        fun build() = Car(this)
    }

    companion object {
        inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
    }
}

// usage
val car = Car.build { 
    model = "John One"
    year = 2017
}

1.4 原型模式

原型模式是以一個對象為原型垛耳,創(chuàng)建出一個新的對象,在 Kotlin 下很容易實(shí)現(xiàn)飘千,因為使用 data class 時堂鲜,會自動獲得equalshashCode护奈、toStringcopy方法缔莲,而copy方法可以克隆整個對象并且允許修改新對象某些屬性。

data class EMail(var recipient: String, var subject: String?, var message: String?)

val mail = EMail("abc@example.com", "Hello", "Don't know what to write.")
val copy = mail.copy(recipient = "other@example.com")

1.5 單例模式

單例模式在 Kotlin 下直接使用object就行了霉旗。想要實(shí)現(xiàn)懶漢式單例或更詳細(xì)的內(nèi)容痴奏,請看之前的文章 Kotlin 設(shè)計模式之單例模式

2. 結(jié)構(gòu)型模式

2.1 適配器模式

適配器模式是把一個不兼容的接口轉(zhuǎn)化為另一個類可以使用的接口厌秒,Kotlin 下的實(shí)現(xiàn)與 Java 一樣读拆。

interface Target {
    fun request()
}

interface Adaptee {
    fun ask()
}

class Adapter(val wrapper: Adaptee) : Target {
    override fun request() {
        wrapper.ask()
    }
}

2.2 橋接模式

橋接模式可以分離某個類存在兩個獨(dú)立變化的緯度,把多層繼承結(jié)構(gòu)改為兩個獨(dú)立的繼承結(jié)構(gòu)鸵闪,在兩個抽象層中有一個抽象關(guān)聯(lián)檐晕,Kotlin 下的實(shí)現(xiàn)與 Java 一樣。

interface Color {
    fun coloring();
}

class RedColor : Color { ... }
class BlueColor : Color { ... }

interface Pen {
    val colorImpl: Color    // this is bridge
    fun write()
}

class BigPen(override val colorImpl: Color) : Pen { ... }
class SmallPen(override val colorImpl: Color) : Pen { ... }

2.3 組合模式

組合模式是對樹形結(jié)構(gòu)的處理蚌讼,讓調(diào)用者忽視單個對象和組合結(jié)構(gòu)的差異辟灰,通常會新增包含葉子節(jié)點(diǎn)和容器節(jié)點(diǎn)接口的抽象構(gòu)件 Component,Kotlin 下的實(shí)現(xiàn)與 Java 一樣篡石。

interface AbstractFile {    // Component
    var childCount: Int
    fun getChild(i: Int): AbstractFile
    fun size(): Long
}

class File(val size: Long, override var childCount: Int = 0) : AbstractFile {
    override fun getChild(i: Int): AbstractFile {
        throw RuntimeException("You shouldn't call the method in File")
    }

    override fun size() = size
}

class Folder(override var childCount: Int) : AbstractFile {
    override fun getChild(i: Int): AbstractFile {
        ...
    }

    override fun size(): Long {
        return (0..childCount)
                .map { getChild(it).size() }
                .sum()
    }
}

2.4 裝飾模式

裝飾模式可以給一個對象添加額外的行為芥喇,在 Kotlin 中可以通過擴(kuò)展函數(shù)簡單的實(shí)現(xiàn)。

class Text(val text: String) {
    fun draw() = print(text)
}

fun Text.underline(decorated: Text.() -> Unit) {
    print("_")
    this.decorated()
    print("_")
}

// usage
Text("Hello").run {
    underline {
        draw()
    }
}

2.5 外觀模式

外觀模式是為一個復(fù)雜的子系統(tǒng)提供一個簡化的統(tǒng)一接口凰萨,Kotlin 下的實(shí)現(xiàn)與 Java 一樣继控,下面我直接使用 dbacinski 的例子械馆。

class ComplexSystemStore(val filePath: String) {
    init {
        println("Reading data from file: $filePath")
    }

    val store = HashMap<String, String>()

    fun store(key: String, payload: String) {
        store.put(key, payload)
    }

    fun read(key: String): String = store[key] ?: ""

    fun commit() = println("Storing cached data: $store to file: $filePath")
}

data class User(val login: String)

//Facade:
class UserRepository {
    val systemPreferences = ComplexSystemStore("/data/default.prefs")

    fun save(user: User) {
        systemPreferences.store("USER_KEY", user.login)
        systemPreferences.commit()
    }

    fun findFirst(): User = User(systemPreferences.read("USER_KEY"))
}

// usage
val userRepository = UserRepository()
val user = User("dbacinski")
userRepository.save(user)
val resultUser = userRepository.findFirst()

2.6 享元模式

享元模式以共享的方式高效地支持大量細(xì)粒度對象的重用,享元對象能做到共享的關(guān)鍵是區(qū)分了可共享內(nèi)部狀態(tài)和不可共享外部狀態(tài)湿诊。Kotlin 下的實(shí)現(xiàn)與 Java 一樣狱杰。

enum class Color {
    black, white
}

open class Chess(val color: Color) { 
    fun display(pos: Pair<Int, Int>) {
        println("The $color chess at position $pos")
    }
}

class BlackChess : Chess(color = Color.black)
class WhiteChess : Chess(color = Color.white)

object ChessFactory {
    private val table = Hashtable<Color, Chess>()
    
    init {
        table.put(Color.black, BlackChess())
        table.put(Color.white, WhiteChess())
    }
    
    fun getChess(color: Color) = table[color]!!
}

// usage
val blackChess = ChessFactory.getChess(Color.black)
val whiteChess = ChessFactory.getChess(Color.white)
blackChess.display(Pair(9, 5))
whiteChess.display(Pair(5, 9))
whiteChess.display(Pair(2, 3))

2.7 代理模式

代理模式是使用一個代理對象來訪問目標(biāo)對象的行為,Kotlin 下的實(shí)現(xiàn)與 Java 一樣厅须,下面我也直接使用 dbacinski 的例子仿畸。

interface File {
    fun read(name: String)
}

class NormalFile : File {
    override fun read(name: String) = println("Reading file: $name")
}

// proxy
class SecuredFile : File {
    val normalFile = NormalFile()
    var password: String = ""

    override fun read(name: String) {
        if (password == "secret") {
            println("Password is correct: $password")
            normalFile.read(name)   // call target object behavior
        } else {
            println("Incorrect password. Access denied!")
        }
    }
}

3. 行為型模式

3.1 職責(zé)鏈模式

職責(zé)鏈模式通過建立一條鏈來組織請求的處理者,請求將沿著鏈進(jìn)行傳遞朗和,請求發(fā)送者無須知道請求在何時错沽、何處以及如何被處理,實(shí)現(xiàn)了請求發(fā)送者與處理者的解耦眶拉。Kotlin 下的實(shí)現(xiàn)與 Java 一樣千埃,看下面這個簡易的 Android 事件傳遞的例子,event 不知道是否被 ViewGroup 攔截并處理忆植。

interface EventHandler {
    var next: EventHandler?
    fun handle(event: MotionEvent): Boolean
}

open class View : EventHandler {
    override var next: EventHandler? = null
    override fun handle(event: MotionEvent): Boolean {
        return onTouchEvent()
    }
    open fun onTouchEvent() : Boolean { 
        ...
        return false 
    }
}

open class ViewGroup : View() {
    override fun handle(event: MotionEvent): Boolean {
        if (onInterceptTouchEvent(event)) return onTouchEvent()
        else return next?.handle(event)!!
    }
    
    open fun onInterceptTouchEvent(event: MotionEvent): Boolean { // 是否攔截事件
        ...
        return false
    }
}

3.2 命令模式

命令模式是將請求封裝為命令對象放可,解耦請求發(fā)送者與接收者,對請求排隊或者記錄請求日志朝刊,以及支持可撤銷的操作耀里。Kotlin 下的實(shí)現(xiàn)與 Java 一樣。

interface Command {
    var value: Int
    val param: Int
    fun execute()
    fun undo()
}

class AddCommand(override var value: Int, override val param: Int) : Command {
    override fun execute() {
        value += param
        println("execute add command and value:$value")
    }
    override fun undo() {
        value -= param
        println("undo add command and value:$value")
    }
}

class MultiplyCommand(override var value: Int, override val param: Int) : Command {
    override fun execute() {
        value *= param
        println("execute multiply command and value:$value")
    }
    override fun undo() {
        value /= param
        println("undo multiply command and value:$value")
    }
}

class Calculator {
    val queue = mutableListOf<Command>()
    fun compute(command: Command) {
        command.execute()
        queue.add(command)
    }
    fun undo() {
        queue.lastOrNull()?.undo()
        queue.removeAt(queue.lastIndex)
    }
}

3.3 解釋器模式

解釋器模式是定義一個語言的文法拾氓,并且建立一個解釋器來解釋該語言中的句子冯挎,這里的“語言”是指使用規(guī)定格式和語法的代碼。因為使用頻率較低咙鞍,而且 Kotlin 中也沒有特殊的實(shí)現(xiàn)房官,所以就不舉例說明了。

3.4 迭代器模式

迭代器模式提供一種遍歷聚合對象中的元素的一種方式续滋,在不暴露底層實(shí)現(xiàn)的情況下翰守。在 Kotlin 下,定義 operator fun iterator() 迭代器函數(shù)疲酌,或者是作為擴(kuò)展函數(shù)時蜡峰,可以在 for 循環(huán)中遍歷。

class Sentence(val words: List<String>)

operator fun Sentence.iterator(): Iterator<String> = words.iterator()
// or
operator fun Sentence.iterator(): Iterator<String> = object : Iterator<String> {
    val iterator = words.iterator()
    
    override fun hasNext() = iterator.hasNext()

    override fun next() = iterator.next()
}

3.5 中介者模式

中介者模式用一個中介對象(中介者)來封裝一系列的對象交互徐勃,中介者使各對象不需要顯式地相互引用,從而使其耦合松散早像,而且可以獨(dú)立地改變它們之間的交互。

interface ChatMediator {
    fun sendMsg(msg: String, user: User)
    fun addUser(user: User)
}

abstract class User(val name: String, val mediator: ChatMediator) {
    abstract fun send(msg: String)
    abstract fun receive(msg: String)
}

class ChatMediatorImpl : ChatMediator {
    private val users = mutableListOf<User>()

    override fun sendMsg(msg: String, user: User) {
        users.filter { it != user }
                .forEach { it.receive(msg) }
    }

    override fun addUser(user: User) {
        users.add(user)
    }
}

class UserImpl(name: String, mediator: ChatMediator) : User(name, mediator) {
    override fun send(msg: String) {
        println("$name : sending messages = $msg")
        mediator.sendMsg(msg, this)
    }

    override fun receive(msg: String) {
        println("$name : received messages = $msg")
    }
}

3.6 備忘錄模式

備忘錄模式是在不破壞封裝的前提下,捕獲一個對象的內(nèi)部狀態(tài)舔哪,并在該對象之外保存這個狀態(tài)虏冻,這樣可以在以后將對象恢復(fù)到原先保存的狀態(tài)劝堪。

data class Memento(val fileName: String, val content: StringBuilder)

class FileWriter(var fileName: String) {
    
    private var content = StringBuilder()
    
    fun write(str: String) {
        content.append(str)
    }
    
    fun save() = Memento(fileName, StringBuilder(content))
    
    fun restore(m: Memento) {
        fileName = m.fileName
        content = m.content
    } 
}

3.7 觀察者模式

觀察者模式是一個對象狀態(tài)發(fā)生變化后,可以立即通知已訂閱的另一個對象揉稚。在 Kotlin 下可以使用 observable properties秒啦,簡化實(shí)現(xiàn)。

interface TextChangedListener {
    fun onTextChanged(newText: String)
}

class TextView {
    var listener: TextChangedListener? = null

    var text: String by Delegates.observable("") { prop, old, new ->
        listener?.onTextChanged(new)
    }
}

3.8 狀態(tài)模式

狀態(tài)模式將一個對象在不同狀態(tài)下的不同行為封裝在一個個狀態(tài)類中搀玖,通過設(shè)置不同的狀態(tài)可以讓對象擁有不同的行為余境。

sealed class UserState(val name:String, val isAuthorized: Boolean) {
    abstract fun click()

    class Unauthorized : UserState(name = "Unauthorized", isAuthorized = false) {
        override fun click() {
            print("User is unauthorized.")
        }
    }

    class Authorized(name: String) : UserState(name, isAuthorized = true) {
        override fun click() {
            print("User is authorized as $name")
        }
    }
}

class User {
    private var state: UserState = UserState.Unauthorized()
    
    val name: String
        get() = state.name
    
    val isAuthorized: Boolean
        get() = state.isAuthorized
    
    fun click() = state.click()
    
    fun login(name: String) {
        state = UserState.Authorized(name)
    }
    
    fun logout() {
        state = UserState.Unauthorized()
    }
}

3.9 策略模式

策略模式用于算法的自由切換和擴(kuò)展,分離算法的定義與實(shí)現(xiàn)灌诅,在 Kotlin 中可以使用高階函數(shù)作為算法的抽象芳来。

class Customer(val name: String, val fee: Double, val discount: (Double) -> Double) {
    fun pricePerMonth() = discount(fee)
}

// usage
val studentDiscount = { fee: Double -> fee/2 }
val noDiscount = { fee: Double -> fee }

val student = Customer("Ned", 10.0, studentDiscount)
val regular = Customer("John", 10.0, noDiscount)

3.10 模版方法模式

模板方法模式提供了一個模板方法來定義算法框架,而某些具體步驟的實(shí)現(xiàn)可以在其子類中完成猜拾,Kotlin 中使用高階函數(shù)可以避免繼承的方式即舌。

class Task {
    fun run(step2: () -> Unit, step3: () -> Unit) {
        step1()
        step2()
        step3()
    }
    
    fun step1() { ... }
}

3.11 訪問者模式

訪問者模式提供一個作用于某對象結(jié)構(gòu)中的各元素的操作表示,它使我們可以在不改變各元素的類的前提下定義作用于這些元素的新操作挎袜。

interface Employee {
    fun accept(visitor: Visitor)
}

class GeneralEmployee(val wage: Int) : Employee {
    override fun accept(visitor: Visitor) = visitor.visit(this)

}

class ManagerEmployee(val wage: Int, val bonus: Int) : Employee {
    override fun accept(visitor: Visitor) = visitor.visit(this)
}

interface Visitor {
    fun visit(ge: GeneralEmployee)
    fun visit(me: ManagerEmployee)
}

class FADVisitor : Visitor {
    override fun visit(ge: GeneralEmployee) {
        println("GeneralEmployee wage:${ge.wage}")
    }
    override fun visit(me: ManagerEmployee) {
        println("ManagerEmployee wage:${me.wage + me.bonus}")
    }
}
// other visitor ...

參考資料

Gang of Four Patterns in Kotlin

Design Patterns

設(shè)計模式 Java 版

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末顽聂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盯仪,更是在濱河造成了極大的恐慌紊搪,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磨总,死亡現(xiàn)場離奇詭異嗦明,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚪燕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門娶牌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人馆纳,你說我怎么就攤上這事诗良。” “怎么了鲁驶?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵鉴裹,是天一觀的道長。 經(jīng)常有香客問我钥弯,道長径荔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任脆霎,我火速辦了婚禮总处,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘睛蛛。我一直安慰自己鹦马,他們只是感情好胧谈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荸频,像睡著了一般菱肖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旭从,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天稳强,我揣著相機(jī)與錄音,去河邊找鬼遇绞。 笑死键袱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摹闽。 我是一名探鬼主播蹄咖,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼付鹿!你這毒婦竟也來了澜汤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤舵匾,失蹤者是張志新(化名)和其女友劉穎俊抵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坐梯,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徽诲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吵血。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谎替。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蹋辅,靈堂內(nèi)的尸體忽然破棺而出钱贯,到底是詐尸還是另有隱情,我是刑警寧澤侦另,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布秩命,位于F島的核電站,受9級特大地震影響褒傅,放射性物質(zhì)發(fā)生泄漏弃锐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一殿托、第九天 我趴在偏房一處隱蔽的房頂上張望霹菊。 院中可真熱鬧,春花似錦碌尔、人聲如沸浇辜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柳洋。三九已至,卻和暖如春叹坦,著一層夾襖步出監(jiān)牢的瞬間熊镣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工募书, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绪囱,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓莹捡,卻偏偏與公主長得像鬼吵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子篮赢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容