使用Kt特点编写设计模式
创建型模式
在程序设计中,我们做得最多得事情之⼀就是去创建⼀个对象。创建对象虽然看起来简单,但实际的业务或许⼗分复杂,这些对象的类可能存在⼦⽗类继承关系,或者代表了系统中各种不同的结构和功能。因此,创建怎样的对象,如何且何时创建它们,以及对类和对象的配置,都是实际代码编写中需要考虑的问题。
工厂模式
简单工厂
简单⼯⼚核⼼作⽤就是通过⼀个⼯⼚类隐藏对象实例的创建逻辑,⽽不需要暴露给客户端。
典型的使⽤场景就是当拥有⼀个 ⽗类与多个⼦类的时候,我们可以通过这种模式来创建⼦类对象。
假设现在有一个电脑加工厂,同时⽣产个⼈电脑和服务器主机。
我们⽤熟悉的⼯⼚模式设计描述其业务逻辑:
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"
}