Kotlin 语言下设计模式的不同实现

偶然在 Github 上看到 dbacinski 写的 Kotlin 语言下设计模式的不同实现(这里的不同是相对于 Java 语言的),有些实现非常好,但是有些实现的例子不是很赞同。所以自己写了 Kotlin 语言版本的 23 种设计模式的实现,充分利用 Kotlin 的语法糖,例如单例模式、策略模式等可以很巧妙地实现,其他实现方式与 Java 不变的也有代码示例,就当是回顾设计模式。

1. 创建型模式

1.1 工厂方法模式

工厂方法把创建对象的过程抽象为接口,由工厂的子类决定对象的创建,Kotlin 下的实现与 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 抽象工厂模式

工厂方法针对一种产品,而抽象工厂是针对一系列产品,为每种产品定义一个工厂方法,工厂子类负责创建该系列的多种产品,Kotlin 下的实现与 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 建造者模式

建造者模式是为了构建复杂对象的,一般情况下,Kotlin 中使用标准的apply函数就可以了,例如下面创建 Dialog 的例子:

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

不过上面的代码中在 apply 里的 lambda 表达式里可以调用 Dialog.show() 等其他与构建对象无关的方法,或者不想公开构造函数,只想通过 Builder 来构建对象,这时可以使用 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 原型模式

原型模式是以一个对象为原型,创建出一个新的对象,在 Kotlin 下很容易实现,因为使用 data class 时,会自动获得equalshashCodetoStringcopy方法,而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就行了。想要实现懒汉式单例或更详细的内容,请看之前的文章 Kotlin 设计模式之单例模式

2. 结构型模式

2.1 适配器模式

适配器模式是把一个不兼容的接口转化为另一个类可以使用的接口,Kotlin 下的实现与 Java 一样。

interface Target {
    fun request()
}

interface Adaptee {
    fun ask()
}

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

2.2 桥接模式

桥接模式可以分离某个类存在两个独立变化的纬度,把多层继承结构改为两个独立的继承结构,在两个抽象层中有一个抽象关联,Kotlin 下的实现与 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 组合模式

组合模式是对树形结构的处理,让调用者忽视单个对象和组合结构的差异,通常会新增包含叶子节点和容器节点接口的抽象构件 Component,Kotlin 下的实现与 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 中可以通过扩展函数简单的实现。

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 外观模式

外观模式是为一个复杂的子系统提供一个简化的统一接口,Kotlin 下的实现与 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 享元模式

享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了可共享内部状态和不可共享外部状态。Kotlin 下的实现与 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 代理模式

代理模式是使用一个代理对象来访问目标对象的行为,Kotlin 下的实现与 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 职责链模式

职责链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。Kotlin 下的实现与 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 命令模式

命令模式是将请求封装为命令对象,解耦请求发送者与接收者,对请求排队或者记录请求日志,以及支持可撤销的操作。Kotlin 下的实现与 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 解释器模式

解释器模式是定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。因为使用频率较低,而且 Kotlin 中也没有特殊的实现,所以就不举例说明了。

3.4 迭代器模式

迭代器模式提供一种遍历聚合对象中的元素的一种方式,在不暴露底层实现的情况下。在 Kotlin 下,定义 operator fun iterator() 迭代器函数,或者是作为扩展函数时,可以在 for 循环中遍历。

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 中介者模式

中介者模式用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

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 备忘录模式

备忘录模式是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

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 观察者模式

观察者模式是一个对象状态发生变化后,可以立即通知已订阅的另一个对象。在 Kotlin 下可以使用 observable properties,简化实现。

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 状态模式

状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态可以让对象拥有不同的行为。

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 策略模式

策略模式用于算法的自由切换和扩展,分离算法的定义与实现,在 Kotlin 中可以使用高阶函数作为算法的抽象。

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 模版方法模式

模板方法模式提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成,Kotlin 中使用高阶函数可以避免继承的方式。

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

3.11 访问者模式

访问者模式提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

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

设计模式 Java 版

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 一、创建型模式 1.1 工厂方法模式 工厂方法把创建对象的过程抽象为接口,由工厂的子类决定对象的创建,Kotlin...
    AWeiLoveAndroid阅读 1,850评论 0 11
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,139评论 9 118
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,428评论 25 707
  • 在熙熙攘攘的车站,周欣依然是很容易让人留下深刻印象的一个。他穿着一身全白的海军短袖夏常服,端端正正地戴着军帽,两只...
    残阳高照阅读 250评论 0 2
  • 1.出名要趁早VS大器晚成 (1)理论越早提出的人首先会让人更容易记住,因为他们够“特别”,年龄小就是一个记忆点。...
    三月Zoro君阅读 158评论 0 2