使用Kt特点编写设计模式

2022-05-14/2022-05-14

创建型模式

在程序设计中,我们做得最多得事情之⼀就是去创建⼀个对象。创建对象虽然看起来简单,但实际的业务或许⼗分复杂,这些对象的类可能存在⼦⽗类继承关系,或者代表了系统中各种不同的结构和功能。因此,创建怎样的对象,如何且何时创建它们,以及对类和对象的配置,都是实际代码编写中需要考虑的问题。

工厂模式

简单工厂

简单⼯⼚核⼼作⽤就是通过⼀个⼯⼚类隐藏对象实例的创建逻辑,⽽不需要暴露给客户端。

典型的使⽤场景就是当拥有⼀个 ⽗类与多个⼦类的时候,我们可以通过这种模式来创建⼦类对象。

假设现在有一个电脑加工厂,同时⽣产个⼈电脑和服务器主机。

我们⽤熟悉的⼯⼚模式设计描述其业务逻辑:

class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computer
enum class ComputerType { PC, Server }
class ComputerFactory {
    fun produce(type: ComputerType): Computer {
        return when (type) {
            ComputerType.PC -> PC() 
            ComputerType.Server -> Server()
        }
    }
}

以上代码通过调⽤ComputerFactory类的produce⽅法来创建不同的Computer⼦类对象,这样我们就把创建实例的逻辑与客户端之间实现解耦,当对象创建的逻辑发⽣变化时(如构造参数的数量发⽣变化),该模式只需要修改produce⽅法内部的代码即可,相⽐直接创

建对象的⽅式更加利于维护。

接下来看怎么使用Kt的语法来进行改造:

1.工厂类采用单例

object ComputerFactory {
    fun produce(type: ComputerType): Computer {
        return when (type) {
            ComputerType.PC -> PC() 
            ComputerType.Server -> Server()
        }
    }
}

现在就能这样进行生产

ComputerFactory.produce(ComputerType.PC)

还可以重载invoke操作符省略produce方法

object ComputerFactory {
    operator fun invoke(type: ComputerType): Computer {
        return when (type) {
            ComputerType.PC -> PC() 
            ComputerType.Server -> Server()
        }
    }
}

现在,可以这样调用

ComputerFactory(ComputerType.PC)

2.伴⽣对象创建静态⼯⼚⽅法

我们是否可以直接通过Computer()⽽不是ComputerFactory()来创建⼀个实例呢?

可以在computer接口中使用伴生类的形式实现

interface Computer {
    val cpu: String
}

interface Computer {
    val cpu: String companion

    object {
        operator fun invoke(type: ComputerType): Computer {
            return when (type) {
                ComputerType.PC -> PC() 
                ComputerType.Server -> Server()
            }
        }
    }
}

抽象工厂

在上面的简单工厂中,我们解决了电脑⼚商⽣产服务器、PC机的问题,但是如果此时有很多种电脑厂商呢?

⽐如现在引⼊了品牌商的概念,我们有好⼏个不同的电脑品牌,⽐如Dell、Asus、Acer,那么就有必要再增加⼀个⼯⼚类。

然⽽,我们并不希望对每种电脑厂商都建⽴⼀个⼯⼚,这会让代码变得难以维护,所以这时候我们就需要引⼊抽象⼯⼚模式。

抽象⼯⼚模式 :为创建⼀组相关或相互依赖的对象提供⼀个接口,⽽且⽆须指定它们的具体类。

interface Computer

abstract class AbstractFactory {
    abstract fun produce(): Computer

    companion object {
        operator fun invoke(factory: AbstractFactory): AbstractFactory {
            return factory
        }
    }
}

class Dell : Computer
class Asus : Computer
class Acer : Computer

class DellFactory : AbstractFactory() {
    override fun produce() = Dell()
}

class AsusFactory : AbstractFactory() {
    override fun produce() = Asus()
}

class AcerFactory : AbstractFactory() {
    override fun produce() = Acer()
}

此时,当每次创建具体的⼯⼚类时,都需要传⼊⼀个具体的⼯⼚类对象作为参数进⾏构造,这个在语法上显然不是很优雅。

可以使用Kotlin中的内联函数来改善这⼀情况

abstract class AbstractFactory {
    abstract fun produce(): Computer

    companion object {
        inline operator fun <reified T : Computer> invoke(): AbstractFactory = when {
            Dell::class -> DellFactory()
            Asus::class -> AsusFactory()
            Acer::class -> AcerFactory()
        }
    }
}

现在就可以这样来创建具体的工厂类

val dellFactory = Abstract<Dell>()

构建者模式

当构建一个对象的构造参数很多时,可能会出现这样的状况:

Robot robot = new Robot(1, true, true, false, false, false, false, false, false)

刚写完时回头看你还能看懂,⼀天后你可能已经忘记⼤半了,再过⼀个星期你已经不知道这是什么东西了。⾯对这样的业务场景时,

我们惯常的做法是通过Builder(构建者)模式来解决。

构建者模式:

将⼀个复杂对象的构建与它的表⽰分离,使得同样的构建过程可以创建不同的表⽰。同时也可以减少构建一个对象时的样板代码

其实我们也可以采⽤重叠构造器模式,即先提供⼀个只有必要参数的构造函数,然后再提供其他更多的构造函数,分别具有不同情况的可选属性。虽然这种模式在调⽤的时候改进不少,但同样存在明显的缺点。因为随着构造函数的参数数量增加,很快我们就会失去控制,代码变得难以维护。

class Robot private constructor(val code: String, val battery: String?, val height: Int?, val weight: Int?) {
    class Builder(val code: String) {
        private var battery: String? = null 
        private var height: Int? = null 
        private var weight: Int? = null

        fun setBattery(battery: String?): Builder {
            this.battery = battery return this
        }

        fun setHeight(height: Int): Builder {
            this.height = height return this
        }

        fun setWeight(weight: Int): Builder {
            this.weight = weight return this
        }

        fun build(): Robot {
            return Robot(code, battery, height, weight)
        }
    }
}

以上的例⼦我们只选择了4个属性,其中code(机器⼈代号)为必需属性,battery(电池)、height(⾼度)、weight(重量)为可选属性。我们再看看如何⽤这种⽅式来声明⼀个Robot对象:

val robot = Robot
.Builder("007") 
.setBattery("R6")
.setHeight(100)
.setWeight(80) 
.build()

这种链式调⽤的设计看起来确实优雅了不少

然⽽,构建者模式也存在⼀些不⾜:

1)如果业务需求的参数很多,代码依然会显得⽐较冗长;

2)你可能会在使⽤Builder的时候忘记在最后调⽤build⽅法;

3)由于在创建对象的时候,必须先创建它的构造器,因此额外增加了多余的开销,在某些⼗分注重性能的情况下,可能就存在⼀定

的问题

我们来看看怎么用KT的特性进行改造

kt中具有可选参数,现在重新设计以上的Robot例⼦

class Robot
(val code: String,
val battery: String? = null,
val height: Int? = null,
val weight: Int? = null)

val robot1 = Robot(code = "007")

我们还可以使用require⽅法对参数进⾏约束

举个例⼦,假设⼀个机器⼈的重量必须根据电池的型号决定,那么在未传⼊电池型号之前,你便不能对weight属性进⾏赋值,否则就会抛出异常

我们可以这样做

fun build(): Robot {
    if (weight != null && battery == null) {
        throw IllegalArgumentException("Battery should be determined when set"
    } else {
        return Robot(code, battery, height, weight)
    }
}

我们也可以这样做

class Robot(val code: String, val battery: String? = null, val height: Int? = null, val weight: Int? = null) {
    init {
        require(weight == null || battery != null) { "Battery should be determined when setting weight." }
    }
}

行为型模式

当我们用创建型模式创建出类对象之后,就需要在不同对象之间划分职责、产生交互。那些用来识别对象之间的常用交流模式就是我们划类为行为型模式。

观察者模式

观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新。

在Java中提供了 java.util.Observable 类 和 java.util.Observer 来帮助实现观察者模式

举一个动态更新股价的例⼦

class StockUpdate : Observable() {
    val observers = mutableSetOf<Observer>();
    fun setStockChanged(price: Int) {
        this.observers.forEach { it.update(this, price) }
    }
}

class StockDisplay : Observer {
    override fun update(o: Observable, price: Any) {
        if (o is StockUpdate) {
            println("The latest stock price is ${price}.")
        }
    }
}

再来看看怎么用Kt进行改变

1.Observable

Kotlin的标准库引⼊了可被观察的委托属性

import kotlin.properties.Delegates

interface StockUpdateListener {
    fun onRise(price: Int)
    fun onFall(price: Int)
}

class StockDisplay : StockUpdateListener {
    override fun onRise(price: Int) {
        println("The latest stock price has risen to ${price}.")
    }

    override fun onFall(price: Int) {
        println("The latest stock price has fell to ${price}.")
    }
}

class StockUpdate {
    var listeners = mutableSetOf<StockUpdateListener>() 
    var price: Int by Delegates.observable(0)
    { _, old, new -> listeners.forEach { if (new > old) it.onRise(price) else it.onFall(price) } }
}

实现java.util.Observer的类只能覆写update⽅法来编写响应逻辑,也就是说如果存在多种不同的逻辑响应,我们也必须通过在该⽅法中进⾏区分实现,显然这会让订阅者的代码显得臃肿

使⽤Delegates.observable()的⽅案更加灵活。它提供了3个参数,依次代表委托属性的元数据KProperty对象、旧值以及新值。

通过额外定义⼀个StockUpdateListener接⼜,我们可以把上涨和下跌的不同响应逻辑封装成接⼜⽅法,从⽽在StockDisplay中实现该接口的onRise和onFall⽅法,实现了解耦。

2.Vetoable

有些时候,我们并不希望监控的值可以被随⼼所欲地修改,此时可以使用vetoable

import kotlin.properties.Delegates 
var value: Int by Delegates.vetoable(0) { prop, old, new -> new > 0 }
>>> value = 1 
>>> println(value) 1
>>> value = -1 
>>> println(value) 1

策略模式

策略模式将类可能有的多个不同的⾏为策略进⾏独⽴封装,与类在逻辑上解耦。 然后根据不同的上下文切换选择不同的策略,然后⽤类对象进⾏调⽤

比如说游泳员具有蛙泳、仰泳、⾃由泳多种行为,我们最开始可以这样设计

class Swimmer {
    fun breaststroke() {
        println("I am breaststroking...")
    }

    fun backstroke() {
        println("I am backstroke...")
    }

    fun freestyle() {
        println("I am freestyling...")
    }
}

然而这并不是一个很好的设计。

首先,并不是所有的游泳运动员都掌握了这3种游泳姿势,如果每个Swimmer类对象都可以调用所有方法,显得比较危险。

其次,后续难免会有新的行为方法加入,通过修改Swimmer类的方式违背了开放封闭原则。

我们可以使用策略模式

这里的类的行为逻辑是游泳,我们将其抽象为游泳策略接口,然后在让具体的游泳策略去实现这个接口

interface SwimStrategy {
    fun swim()
}

class Breaststroke : SwimStrategy {
    override fun swim() {
        println("I am breaststroking...")
    }
}

class Backstroke : SwimStrategy {
    override fun swim() {
        println("I am backstroke...")
    }
}

class Freestyle : SwimStrategy {
    override fun swim() {
        println("I am freestyling...")
    }
}

然后可以在游泳员中传入不同的游泳策略(Context)从而让这个游泳员具有不同的游泳行为

class Swimmer(val strategy: SwimStrategy) {
    fun swim() {
        strategy.swim()
    }
}

fun main(args: Array<String>) {
    val weekendShaw = Swimmer(Freestyle()) weekendShaw.swim()
    val weekdaysShaw = Swimmer(Breaststroke()) weekdaysShaw.swim ()
}

现在再来看看怎么使用kt中的高阶函数来改造

显然将策略封装成⼀个函数然后作为参数传递给Swimmer类会更加的简洁

fun breaststroke() {
    println("I am breaststroking...")
}

fun backstroke() {
    println("I am backstroking...")
}

fun freestyle() {
    println("I am freestyling...")
}

class Swimmer(val swimming: () -> Unit) {
    fun swim() {
        swimming()
    }
}

fun main(args: Array<String>) {
    val weekendShaw = Swimmer(::freestyle) weekendShaw . swim ()
    val weekdaysShaw = Swimmer(::breaststroke) weekdaysShaw . swim ()
}

模板方法模式

模板⽅法模式的定义:

定义⼀个算法中的操作框架,⽽将⼀些步骤延迟到⼦类中,使得⼦类可以不改变算法的结构即可重定义该算法的某些特定步骤。

以去政府大楼办事为例,我们通常有这些步骤

1)排队取号等待;

2)根据⾃⼰的需求办理个性化的业务

3)对服务⼈员的态度进⾏评价。

其中1,3是固定的我们可以在父类中实现,而2是个性化的,我们让其在特定的子类中实现

abstract class CivicCenterTask {
    fun execute() {
        this.lineUp() 
        this.askForHelp() 
        this.evaluate()
    }

    private fun lineUp() {
        println("line up to take a number"); }

    private fun evaluate() {
        println("evaluaten service attitude"); }

    abstract fun askForHelp()
}

class PullSocialSecurity : CivicCenterTask {
    override fun askForHelp() {
        println("ask for pulling the social security")
    }
}

class ApplyForCitizenCard : CivicCenterTask {
    override fun askForHelp() {
        println("apply for a citizen card")
    }
}

我们可以用Kt的高阶函数来避免编写多个子类

class CivicCenterTask {
    fun execute(askForHelp: () -> Unit) {
        this.lineUp() 
        askForHelp () 
        this.evaluate()
    }

    private fun lineUp() {
        println("line up to take a number"); }

    private fun evaluate() {
        println("evaluaten service attitude"); }
}

fun pullSocialSecurity() {
    println("ask for pulling the social security")
}

fun applyForCitizenCard() {
    println("apply for a citizen card")
}

结构型模式

关注于对象的组成以及对象之间的依赖关系,描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

装饰者模式/静态代理模式

在不必改变原类⽂件和使⽤继承的情况下,动态地扩展⼀个对象的功能。该模式通过创建⼀个包装对象,来包裹真实的对象。

interface MacBook {
    fun getCost(): Int
    fun getDesc(): String
    fun getProdDate(): String
}

class MacBookPro : MacBook {
    override fun getCost() = 10000 
    override fun getDesc() = "Macbook Pro" 
    override fun getProdDate() = "Late 2011"
}

class ProcessorUpgradeMacbookPro(val macbook: MacBook) : MacBook by MacBookPro() 
override fun getCost() = macbook.getCost() + 219 
override fun getDesc() = macbook.getDesc() + ", +1G Memory" 
}


标题:使用Kt特点编写设计模式
作者:OkAndGreat
地址:http://zhongtai521.wang/articles/2022/05/14/1652519347274.html

评论
发表评论