烟台网站制作,各网站提交入口,京东网站建设的基本情况,企业管理系统项目经历怎么写简历范文创建型模式
主流的创建型模式有#xff1a;工厂方法模式、抽象工厂模式、构建者模式
伴生对象增强工厂模式
在有些地方会把工厂模式细分为简单工厂、工厂方法模式以及抽象工厂。
这里主要介绍简单工厂的模式#xff0c;它的核心作用就是通过一个工厂类隐藏对象实例的创建…创建型模式
主流的创建型模式有工厂方法模式、抽象工厂模式、构建者模式
伴生对象增强工厂模式
在有些地方会把工厂模式细分为简单工厂、工厂方法模式以及抽象工厂。
这里主要介绍简单工厂的模式它的核心作用就是通过一个工厂类隐藏对象实例的创建逻辑而不需要暴露给客户端。典型的使用场景就是当拥有一个父类与多个子类的时候我们可以通过这种模式来创建子类对象。
假设现在有一个电脑加工厂同时生产个人电脑和服务器主机。我们用熟悉的工厂模式设计描述其业务逻辑
interface Computer {val cpu: String
}class PC(override val cpu: String Core) : Computer
class Server(override val cpu: String Xeon) : Computerenum class ComputerType { PC, Server }class ComputerFactory {fun produce(type: ComputerType): Computer {return when (type) {ComputerType.PC - PC()ComputerType.Server - Server()}}
}
fun main() {val compter ComputerFactory().produce(ComputerType.PC)println(compter.cpu)
}以上代码通过调用ComputerFactory类的produce方法来创建不同的Computer子类对象这样我们就把创建实例的逻辑与客户端之间实现解耦当对象创建的逻辑发生变化时如构造参数的数量发生变化该模式只需要修改produce方法内部的代码即可相比直接创建对象的方式更加利于维护。
用单例代替工厂类
我们已经知道的是Kotlin 支持用 object 来实现 Java 中的单例模式。所以我们可以实现一个 ComputerFactory 单例而不是一个工厂类。
object ComputerFactory { // 用 object 代替 classfun produce(type: ComputerType): Computer {return when (type) {ComputerType.PC - PC()ComputerType.Server - Server()}}
}
fun main() {val compter ComputerFactory.produce(ComputerType.PC)println(compter.cpu)
}我们可以通过operator操作符重载invoke方法来代替produce从而进一步简化表达
object ComputerFactory { operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC - PC()ComputerType.Server - Server()}}
}
fun main() {val compter ComputerFactory(ComputerType.PC)println(compter.cpu)
}伴生对象创建静态工厂方法
我们是否可以直接通过 Computer() 而不是 ComputerFactory() 来创建一个实例呢
考虑用静态工厂方法代替构造器。相信你已经想到了 Kotlin 中的伴生对象它代替了 Java 中的static同时在功能和表达上拥有更强的能力。通过在 Computer 接口中定义一个伴生对象我们就能够实现以上的需求代码如下
interface Computer {val cpu: Stringcompanion object {operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC - PC()ComputerType.Server - Server()}}}
}class PC(override val cpu: String Core) : Computer
class Server(override val cpu: String Xeon) : Computerenum class ComputerType { PC, Server }fun main() {val compter Computer(ComputerType.PC)println(compter.cpu)
}在不指定伴生对象名字的情况下我们可以直接通过 Computer 来调用其伴生对象中的方法。当然如果你喜欢伴生对象有名字我们还是可以命名 Computer 的伴生对象如下用 Factory 来命名
interface Computer {val cpu: Stringcompanion object Factory {operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC - PC()ComputerType.Server - Server()}}}
}fun main() {val compter Computer.Factory(ComputerType.PC)println(compter.cpu)
}注意即便伴生对象是有名字的情况下在调用时依然可以省略显示指定的名字。
扩展伴生对象方法
假设实际业务中我们是Computer接口的使用者比如它是工程引入的第三方类库所有的类的实现细节都得到了很好地隐藏。那么如果我们希望进一步改造其中的逻辑Kotlin 中伴生对象的方式同样可以依靠其扩展函数的特性很好地实现这一需求。
比如我们希望给Computer增加一种功能通过CPU型号来判断电脑类型那么就可以如下实现
fun Computer.Companion.fromCPU(cpu: String): ComputerType? when(cpu) {Core - ComputerType.PCXeon - ComputerType.Serverelse - null
}如果指定了伴生对象的名字为Factory那么就可以如下实现
fun Computer.Factory.fromCPU(cpu: String): ComputerType? when(cpu) {Core - ComputerType.PCXeon - ComputerType.Serverelse - null
}调用
fun main() {val type Computer.fromCPU(Core)println(type)
}内联函数简化抽象工厂
Kotlin中 的内联函数有一个很大的作用就是可以具体化参数类型。利用这一特性我们还可以改进一种更复杂的工厂模式称为抽象工厂。
工厂模式已经能够很好地处理一个产品等级结构的问题在上一节中我们已经用它很好地解决了电脑厂商生产服务器、PC机的问题。进一步思考当问题上升到多个产品等级结构的时候比如现在引入了品牌商的概念我们有好几个不同的电脑品牌比如 Dell、Asus、Acer那么就有必要再增加一个工厂类。然而我们并不希望对每个模型都建立一个工厂这会让代码变得难以维护所以这时候我们就需要引入抽象工厂模式。
抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口而且无须指定它们的具体类。
在抽象工厂的定义中我们也可以把“ 一组相关或相互依赖的对象” 称作 “产品族”在上述的例子中我们就提到了3个代表不同电脑品牌的产品族。
下面我们就利用抽象工厂来实现具体的需求
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()
}abstract class AbstractFactory {abstract fun produce(): Computercompanion object {operator fun invoke(factory: AbstractFactory): AbstractFactory {return factory}}
} fun main() { val dellFactory AbstractFactory(DellFactory())val dell dellFactory.produce()println(dell)
}以上代码当你每次创建具体的工厂类时都需要传入一个具体的工厂对象作为参数进行构造这个在语法上显然不是很优雅。
下面我们可以用 Kotlin 中的内联函数来改善这一情况。我们所需要做的就是去重新实现 AbstractFactory 类中的 invoke 方法。
abstract class AbstractFactory {abstract fun produce(): Computercompanion object {inline operator fun reified T : Computer invoke(): AbstractFactory when(T::class) {Dell::class - DellFactory()Asus::class - AsusFactory()Acer::class - AcerFactory()else - throw IllegalArgumentException()}}
}fun main() { val dellFactory AbstractFactoryDell()val dell dellFactory.produce()println(dell)
}1通过将invoke方法用inline定义为内联函数我们就可以引入reified关键字使用具体化参数类型的语法特性2要具体化的参数类型为Computer在invoke方法中我们通过判断它的具体类型来返回对应的工厂类对象。
用具名可选参数而不是构建者模式
在 Java 开发中你是否写过这样像蛇一样长的构造函数
// Boolean 类型的参数表示 Robot 是否含有对应固件
Robot robot new Robot(1, true, true, false, false, false, false, false, false)刚写完时回头看你还能看懂一天后你可能已经忘记大半了再过一个星期你已经不知道这是什么东西了。面对这样的业务场景时我们惯常的做法是通过 Builder构建者模式来解决。
构建者模式
构建者模式主要做的事情就是将一个复杂对象的构建与它的表示分离使得同样的构建过程可以创建不同的表示。
工厂模式和构造函数都存在相同的问题就是不能很好地扩展到大量的可选参数。假设我们现在有个机器人类它含有多个属性代号、名字、电池、重量、高度、速度、音量等。很多产品都不具有其中的某些属性比如不能走、不能发声甚至有的机器人也不需要电池。
一种糟糕的做法就是设计一个在上面你所看到Robot类把所有的属性都作为构造函数的参数。或者你也可能采用过重叠构造器telescoping constructor模式即先提供一个只有必要参数的构造函数然后再提供其他更多的构造函数分别具有不同情况的可选属性。虽然这种模式在调用的时候改进不少但同样存在明显的缺点。因为随着构造函数的参数数量增加很快我们就会失去控制代码变得难以维护。
构建者模式可以避免以上的问题我们用 Kotlin 来实现 Java 中的构建者模式
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? nullprivate var height: Int? nullprivate var weight: Int? nullfun setBattery(battery: String?): Builder {this.battery batteryreturn this}fun setHeight(height: Int): Builder {this.height heightreturn this}fun setWeight(weight: Int): Builder {this.weight weightreturn this}fun build(): Robot {return Robot(code, battery, height, weight)}}
}fun main() {val robot Robot.Builder(007).setBattery(R6).setHeight(100).setWeight(80).build()
}这种链式调用的设计看起来确实优雅了不少同时对于可选参数的设置也显得比较语义化它有点近似柯里化语法。此外构建者模式另外一个好处就是解决了多个可选参数的问题当我们创建对象实例时只需要用set方法对需要的参数进行赋值即可。
然而构建者模式也存在一些不足
1如果业务需求的参数很多代码依然会显得比较冗长2你可能会在使用Builder的时候忘记在最后调用build方法3由于在创建对象的时候必须先创建它的构造器因此额外增加了多余的开销在某些十分注重性能的情况下可能就存在一定的问题。
事实上当用 Kotlin 设计程序时我们可以在绝大多数情况下避免使用构建者模式。《Effective Java》在介绍构建者模式时是这样描述它的本质上 Builder 模式模拟了具名的可选参数就像 Ada 和 Python 中的一样。幸运的是Kotlin 也是这样一门拥有具名可选参数的编程语言。
具名的可选参数
Kotlin 中的函数和构造器都支持这一特性现在再来回顾下。它主要表现为两点
1在具体化一个参数的取值时可以通过带上它的参数名而不是它在所有参数中的位置决定2由于参数可以设置默认值这允许我们只给出部分参数的取值而不必是所有的参数。
因此我们可以直接使用 Kotlin 中原生的语法特性来实现构建者模式的效果。现在重新设计以上的 Robot 例子
class Robot(val code: String,val battery: String? null,val height: Int? null,val weight: Int? null
)val robot1 Robot(code 007)
val robot2 Robot(code 007, battery R6)
val robot3 Robot(code 007, height 100, weight 80)可以发现相比构建者模式通过具名的可选参数构造类具有很多优点
1代码变得十分简单这不仅表现在Robot类的结构体代码量我们在声明Robot对象时的语法也要更加简洁2声明对象时每个参数名都可以是显式的并且无须按照顺序书写非常方便灵活3由于Robot类的每个对象都是val声明的相较构建者模式者中用var的方案更加安全这在要求多线程并发安全的业务场景中会显得更有优势。
此外如果你的类的功能足够简单更好的思路是用data class直接声明一个数据类。如你所知数据类同样支持以上的所有特性。
require 方法对参数进行约束
构建者模式的另外一个作用就是可以在build方法中对参数添加约束条件。
举个例子假设一个机器人的重量必须根据电池的型号决定那么在未传入电池型号之前你便不能对weight属性进行赋值否则就会抛出异常。
fun build(): Robot {if (weight ! null battery null) {throw IllegalArgumentException(Battery should be determined when setting weight)} else {return Robot(code, battery, height, weight)}
}运行下具体的测试用例
val robot Robot.Builder(007).setWeight(100).build()然后就会发现以下的异常信息
Exception in thread main java.lang.IllegalArgumentException:Battery should be determined when setting weight这种在build方法中对参数进行约束的手段可以让业务变得更加安全。那么通过具名的可选参数来构造类的方案该如何实现呢
显然我们同样可以在Robot类的init方法中增加以上的校验代码。然而在 Kotlin 中我们在类或函数中还可以使用require关键字进行函数参数限制本质上它是一个内联的方法有点类似于 Java 中的assert。
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.}}
}如果我们在创建Robot对象时有不符合require条件的行为就会导致抛出异常。
val robot Robot(code007, weight 100)java.lang.IllegalArgumentException: Battery should be determined when setting weight可见Kotlin 的require方法可以让我们的参数约束代码在语义上变得更加友好。
总的来说在 Kotlin 中我们应该尽量避免使用构建者模式因为 Kotlin 支持具名的可选参数这让我们可以在构造一个具有多个可选参数类的场景中设计出更加简洁并利于维护的代码。
行为型模式
主流的行为型模式有观察者模式、策略模式、模板方法模式、迭代器模式、责任链模式及状态模式。
Kotlin 中的观察者模式
观察者模式定义了一个一对多的依赖关系让一个或多个观察者对象监听一个主题对象。这样一来当被观察者状态发生改变时需要通知相应的观察者使这些观察者对象能够自动更新。
简单来说观察者模式无非做两件事情
订阅者观察者observer添加或删除对发布者被观察者的状态监听发布者状态改变时将事件通知给监听它的所有观察者然后观察者执行响应逻辑。
Observable
Java 自身的标准库提供了 java.util.Observable 类 和 java.util.Observer 接口来帮助实现观察者模式。
下面用它们来实现一个动态更新股价的例子
import java.util.*class StockUpdate: Observable() {val observers mutableSetOfObserver();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}.)}}
}
fun main() {val su StockUpdate()val sd StockDisplay()su.observers.add(sd)su.setStockChanged(100)
}在上述例子中创建了一个可被观察的发布者类StockUpdate它维护了一个监听其变化的观察者对象observers通过它的add和remove方法来进行管理。当StockUpdate类对象执行setStockChanged方法之后那么就会将更新的股价传递给观察者执行其update方法来执行响应逻辑。
Delegates.Observable
事实上Kotlin 的标准库额外引入了可被观察的委托属性也可以利用它来实现同样的场景。
import kotlin.properties.Delegatesinterface 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 mutableSetOfStockUpdateListener()var price: Int by Delegates.observable(0) { _, old, new -listeners.forEach {if (new old) it.onRise(price) else it.onFall(price)}}
}fun main() {val su StockUpdate()val sd StockDisplay()su.listeners.add(sd)su.price 100su.price 98
}在该版本中我们实现了更加具体的需求当股价上涨或下跌时打印不同的个性化报价文案。
如果你仔细思考会发现实现java.util.Observer接口的类只能覆写update方法来编写响应逻辑也就是说如果存在多种不同的逻辑响应我们也必须通过在该方法中进行区分实现显然这会让订阅者的代码显得臃肿。
换个角度如果我们把发布者的事件推送看成一个第三方服务那么它提供的 API 接口只有一个API 调用者必须承担更多的职能。
显然使用 Delegates.observable() 的方案更加灵活。它提供了 3 个参数依次代表委托属性的元数据KProperty对象、旧值以及新值。
通过额外定义一个StockUpdateListener接口我们可以把上涨和下跌的不同响应逻辑封装成接口方法从而在StockDisplay中实现该接口的onRise和onFall方法实现了解耦。
Delegates.Vetoable
有些时候我们并不希望监控的值可以被随心所欲地修改。Kotlin 的标准库中除了observable这个委托属性之外还提供了一个 vetoable 属性顾名思义veto代表的是“ 否决” 的意思vetoable提供了一种功能在被赋新值生效之前提前进行截获然后判断是否接受它。
例如
import kotlin.properties.Delegatesvar value: Int by Delegates.vetoable(0) { prop, old, new -new 0
}value 1
println(value)1value -1
println(value)1这里创建了一个可变的Int对象value同时用by关键字增加了Delegates.vetoable委托属性。它的初始化值为0只接收被正整数赋值。所以当我们试图把value改成-1的时候打印的结果仍然为旧值1。
高阶函数简化策略模式、模板方法模式
遵循开闭原则策略模式
假设现在有一个表示游泳运动员的抽象类Swimmer有一个游泳的方法swim表示如下
class Swimmer {fun swim() {println(I am swimming...)}
}fun main() {val shaw Swimmer()shaw.swim()
}由于shaw在游泳方面很有天赋他很快掌握了蛙泳、仰泳、自由泳多种姿势。所以我们必须对swim方法进行改造变成代表3种不同游泳姿势的方法。
class Swimmer {fun breaststroke() {println(I am breaststroking...)}fun backstroke() {println(I am backstroke...)}fun freestyle() {println(I am freestyling...)}
}然而这并不是一个很好的设计。首先并不是所有的游泳运动员都掌握了这3种游泳姿势如果每个Swimmer类对象都可以调用所有方法显得比较危险。其次后续难免会有新的行为方法加入通过修改Swimmer类的方式违背了开放封闭原则。
因此更好的做法是将游泳这个行为封装成接口根据不同的场景我们可以调用不同的游泳方法。策略模式就是一种解决这种场景很好的思路。
策略模式定义了算法族分别封装起来让它们之间可以相互替换此模式让算法的变化独立于使用算法的客户。
本质上策略模式做的事情就是将不同的行为策略Strategy进行独立封装与类在逻辑上解耦。然后根据不同的上下文Context切换选择不同的策略然后用类对象进行调用。下面我们用熟悉的方式重新实现游泳的例子
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...)}
}
class Swimmer(private val strategy: SwimStrategy) {fun swim() {strategy.swim()}
}
fun main() {// tom会自由泳val tom Swimmer(Freestyle())tom.swim()// jack会蛙泳val jack Swimmer(Breaststroke())jack.swim()
}这个方案实现了解耦和复用的目的且很好实现了在不同场景切换采用不同的策略。然而该版本的代码量也比之前多了很多。
高阶函数抽象算法
我们用高阶函数的思路来重新思考下策略类显然将策略封装成一个函数然后作为参数传递给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() {// tom会自由泳val tom Swimmer(::freestyle)tom.swim()// jack会蛙泳val jack Swimmer(::breaststroke)jack.swim()
}可以看到代码量骤减且结构也清晰易读。由于策略算法都封装成了一个个函数我们在初始化Swimmer类对象时可以用函数引用的语法传递构造参数。当然我们也可以把函数用val声明成Lambda表达式那么在传递参数的时候会变得更加简洁直观。
模板方法模式高阶函数代替继承
另一个可用高阶函数函数改良的设计模式就是模板方法模式。
某种程度上模板方法模式和策略模式要解决的问题是相似的它们都可以分离通用的算法和具体的上下文。然而如果说策略模式采用的思路是将算法进行委托那么传统的模板方法模式更多是基于继承的方式实现的。
来看看模板方法模式的定义
定义一个算法中的操作框架而将一些步骤延迟到子类中使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
与策略模式不同模板方法模式的行为算法具有更清晰的大纲结构其中完全相同的步骤会在抽象类中实现可个性化的某些步骤在其子类中进行定义。
举个例子如果我们去市民事务中心办事时一般都会有以下几个具体的步骤
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()
}其中askForHelp方法是一个抽象方法。接下来我们再定义具体的子类来继承CivicCenterTask类然后对抽象的步骤进行实现。
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)}
}调用
fun main() {val task PullSocialSecurity()task.execute()val task2 ApplyForCitizenCard()task2.execute()
}在 Kotlin 中我们同样可以用改造策略模式的类似思路来简化模板方法模式。把抽象的部分使用高阶函数来传递。
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)
}fun main() {val task1 CivicCenterTask()task1.execute(::pullSocialSecurity)val task2 CivicCenterTask()task2.execute(::applyForCitizenCard)
}可见在高阶函数的帮助下我们可以更加轻松地实现模板方式模式。
运算符重载和迭代器模式
有些时候我们会定义某些容器类这些类中包含了大量相同类型的对象。如果你想给这个容器类的对象直接提供迭代的方法如hasNext、next、first等那么就可以自定义一个迭代器。然而通常情况下我们不需要自己再实现一个迭代器因为Java标准库提供了java.util.Iterator接口你可以用容器类实现该接口然后再实现需要的迭代器方法。
这种设计模式就是迭代器模式它的核心作用就是将遍历和实现分离开来在遍历的同时不需要暴露对象的内部表示。
实现Iterator接口的简单例子
data class Book(val name:String)class Bookcase(books: ListBook): IteratorBook {private val iterator: IteratorBook books.iterator()override fun hasNext() this.iterator.hasNext()override fun next() this.iterator.next()
}fun main() {val bookcase Bookcase(listOf(Book(DiveintoKotlin),Book(ThinkinginJava)))while (bookcase.hasNext()) {println(Thebooknameis${bookcase.next().name})}
}由于Bookcase对象拥有与ListBook实例相同的迭代器我们就可以直接调用后者迭代器所有的方法。
当然我们一般会使用更简洁的遍历打印方式如下
for (book in bookcase) {println(The book name is ${book.name})
}重载 iterator 方法
Kotlin 还有更好的解决方案。Kotlin 有一个非常强大的语言特性那就是利用operator关键字内置了很多运算符重载功能。
我们就可以通过重载Bookcase类的iterator方法实现一种语法上更加精简的版本
data class Book(val name:String)class Bookcase(val books: ListBook) {operator fun iterator(): IteratorBook this.books.iterator()
}我们用一行代码就实现了以上所有的效果。还没完由于 Kotlin 还支持扩展函数这意味着我们可以给所有的对象都内置一个迭代器。
通过扩展函数重载 iterator 方法
假设现在的Bookcase是引入的一个类你并不能修改它的源码下面我们就演示如何用扩展的语法来给Bookcase类对象增加迭代的功能
data class Book(val name: String)
class Bookcase(val books: ListBook) {}
operator fun Bookcase.iterator(): IteratorBook books.iterator()代码依旧非常简洁假如你想对迭代器的逻辑有更多的控制权那么也可以通过object表达式来实现
operator fun Bookcase.iterator(): IteratorBook object : IteratorBook {val iterator books.iterator()override fun hasNext() iterator.hasNext()override fun next() iterator.next()
}总的来说迭代器模式并不是一种很常用的设计模式但通过它我们可以进一步了解 Kotlin 中的扩展函数的应用以及运算符重载功能的强大之处。
用偏函数实现责任链模式
简单来说责任链模式的目的就是避免请求的发送者和接收者之间的耦合关系将这个对象连成一条链并沿着这条链传递该请求直到有一个对象处理它为止。
现在我们来举一个更加具体的例子。计算机学院的学生会管理了一个学生会基金用于各种活动和组织人员工作的开支。当要发生一笔支出时如果金额在100元之内可由各个分部长审批如果金额超过了100元那么就需要会长同意但假使金额较大达到了500元以上那么就需要学院的辅导员陈老师批准。此外学院里还有一个不宣的规定经费的上限为1000元如果超出则默认打回申请。
当然我们可以用最简单的if-else来实现经费审批的需求。然而根据开闭原则我们需要将其中的逻辑进行解耦。下面我们就用面向对象的思路结合责任链模式来设计一个程序。
data class ApplyEvent(val money: Int, val title: String)interface ApplyHandler {val successor: ApplyHandler?fun handleEvent(event: ApplyEvent)
}class GroupLeader(override val successor: ApplyHandler?): ApplyHandler {override fun handleEvent(event: ApplyEvent) {when {event.money 100 - println(Group Leader handled application: ${event.title})else - when(successor) {is ApplyHandler - successor.handleEvent(event)else - println(Group Leader: This application cannot be handdle.)}}}
}class President(override val successor: ApplyHandler?): ApplyHandler {override fun handleEvent(event: ApplyEvent) {when {event.money 500 - println(President handled application: ${event.title})else - when(successor) {is ApplyHandler - successor.handleEvent(event)else - println(President: This application cannot be handdle.)}}}
}class College(override val successor: ApplyHandler?): ApplyHandler {override fun handleEvent(event: ApplyEvent) {when {event.money 1000 - println(College: This application is refused.)else - println(College handled application: ${event.title}.)}}
}fun main() {val college College(null)val president President(college)val groupLeader GroupLeader(president)groupLeader.handleEvent(ApplyEvent(10, buy a pen))groupLeader.handleEvent(ApplyEvent(200, team building))groupLeader.handleEvent(ApplyEvent(600, hold a debate match))groupLeader.handleEvent(ApplyEvent(1200, annual meeting of the college))
}运行结果
Group Leader handled application: buy a pen.
President handled application: team building.
College handled application: hold a debate match.
College: This application is refused.在这个例子中我们声明了GroupLeader、President、College三个类来代表学生会部长、分部长、会长及学院它们都实现了ApplyHandler接口。接口包含了一个可空的后继者对象successor以及对申请事件的处理方法handleEvent。当我们把一个申请经费的事件传递给GroupLeader对象进行处理时它会根据具体的经费金额来判断是否将申请转交给successor对象也就是President类来处理。以此类推最终形成了一个责任链的机制。
现在我们再来重新思考下责任链的机理你会发现整个链条的每个处理环节都有对其输入参数的校验标准在上述例子中主要是对申请经费事件的金额有要求。当输入参数处于某个责任链环节的有效接收范围之内该环节才能对其做出正常的处理操作。在编程语言中我们有一个专门的术语来描述这种情况这就是“偏函数” 。
实现偏函数类型PartialFunction
什么是偏函数
偏函数是个数学中的概念指的是定义域X中可能存在某些值在值域Y中没有对应的值。
为了方便理解我们可以把偏函数与普通函数进行比较。在一个普通函数中我们可以给指定类型的参数传入任意该类型的值比如(Int) - Unit可以接收任何Int值。而在一个偏函数中输入类型的参数值不一定被接收比如
fun mustGreaterThan5(x: Int): Boolean {if (x 5) return trueelse throw Exception(x must be greator than 5)
}mustGreaterThan5(6)truemustGreaterThan5(1)java.lang.Exception: x must be greator than 5 at Line57.mustGreaterThan5(Unknown Source) // 必须传入大于5的值之所以提到偏函数是因为在一些函数式编程语言中如 Scala有一种PartialFunction类型我们可以用它来简化责任链模式的实现。由于 Kotlin 的语言特性足够灵活强大虽然它的标准库并没有支持 PartialFunction然而一些开源库已经实现了这个功能。我们来看看如何定义PartialFunction类型
class PartialFunctionin P1, out R(private val defineAt : (P1) - Boolean,private val f : (P1) - R
) : (P1) - R {override fun invoke(p1 : P1) : R {if (defineAt(p1)) {return f(p1)} else {throw IllegalArgumentException(Value: ($p1) isnt supported by this function)}}fun isDefinedAt(p1: P1) defineAt(p1)
}PartialFunction类的具体作用
声明类对象时需接收两个构造参数其中definetAt为校验函数f为处理函数当PartialFunction类对象执行invoke方法时definetAt会对输入参数p1进行有效性校验如果校验结果通过则执行f函数同时将p1作为参数传递给它反之则抛出异常。
PartialFunction类可以解决责任链模式中各个环节对于输入的校验及处理逻辑的问题但是依旧有一个问题需要解决就是如何将请求在整个链条中进行传递。
接下来我们再利用 Kotlin 的扩展函数给 PartialFunction 类增加一个 orElse 方法。在此之前我们先注意下这个类中的isDefinedAt方法它其实并没有什么特殊之处仅仅只是作为拷贝definetAt的一个内部方法为了在orElse方法中能够被调用。
infix fun P1, R PartialFunctionP1, R.orElse(that: PartialFunctionP1, R): PartialFunctionP1, R {return PartialFunction({ this.isDefinedAt(it) || that.isDefinedAt(it) }) {when {this.isDefinedAt(it) - this(it)else - that(it)}}
}在orElse方法中可以传入另一个PartialFunction类对象that它也就是责任链模式中的后继者。当isDefinedAt方法执行结果为false的时候那么就调用that对象来继续处理申请。
这里用infix关键字来让orElse成为一个中缀函数从而让链式调用的语法变得更加直观。
用 orElse 构建责任链
接下来我们就用设计好的PartialFunction类及扩展的orElse方法来重新实现一下最开始的例子。
val groupLeader run {val definetAt: (ApplyEvent) - Boolean { it.money 200 }val handler: (ApplyEvent) - Unit { println( groupLeader ... ) }PartialFunction(definetAt, handler)
}val president run {val definetAt: (ApplyEvent) - Boolean { it.money 500 }val handler: (ApplyEvent) - Unit { println( president ... ) }PartialFunction(definetAt, handler)
}val college run {val definetAt: (ApplyEvent) - Boolean { true }val handler: (ApplyEvent) - Unit { println( college ... ) }PartialFunction(definetAt, handler)
}然后调用如下
fun main() {val applyChain groupLeader orElse president orElse collegeapplyChain(ApplyEvent(10, buy a pen))applyChain(ApplyEvent(200, team building))applyChain(ApplyEvent(600, hold a debate match))applyChain(ApplyEvent(1200, annual meeting of the college))
}这里用orElse获得了更好的语法表达。
ADT 实现状态模式
状态模式与策略模式存在某些相似性它们都可以实现某种算法、业务逻辑的切换。以下是状态模式的定义
状态模式允许一个对象在其内部状态改变时改变它的行为对象看起来似乎修改了它的类。
状态模式具体表现在
状态决定行为对象的行为由它内部的状态决定。对象的状态在运行期被改变时它的行为也会因此而改变。 从表面上看同一个对象在不同的运行时刻行为是不一样的就像是类被修改了一样。
再次与策略模式做比较你也会发现两种模式之间的不同
策略模式通过在客户端切换不同的策略实现来改变算法而在状态模式中对象通过修改内部的状态来切换不同的行为方法。
来看个饮水机的例子假设一个饮水机有 3 种工作状态分别为未启动、制冷模式、制热模式。可以用密封类来封装一个代表不同饮水机状态的 ADT。
class WaterMachine {var state : WaterMachineState WaterMachineState.Off(this)fun turnHeating() {this.state.turnHeating()}fun turnCooling() {this.state.turnCooling()}fun turnOff() {this.state.turnOff()}
}sealed class WaterMachineState(open val machine: WaterMachine) {class Off(override val machine: WaterMachine): WaterMachineState(machine)class Heating(override val machine: WaterMachine): WaterMachineState(machine)class Cooling(override val machine: WaterMachine): WaterMachineState(machine)fun turnHeating() {if (this !is Heating) { machine.state Heating(machine)println(turn heating)} else {println(The state is already heating mode.)}}fun turnCooling() {if (this !is Cooling) { machine.state Cooling(machine)println(turn cooling)} else {println(The state is already cooling mode.)}}fun turnOff() {if (this !is Off) { machine.state Off(machine)println(turn off)} else {println(The state is already off.)}}
}利用上面的ADT数据结构来实现这样一个需求Shaw早上上班的时候会把饮水机调整为制冷模式他想泡面的时候就会把饮水机变为制热所以每次他吃了泡面下一个喝水的同事就需要再切换回制冷。最后要下班了Kim就会关闭饮水机的电源。
enum class Moment{EARLY_MORNING, // 早上上班DRINKING_WATER, // 日常饮水INSTANCE_NOODLES,// Shaw吃泡面AFTER_WORK // 下班
}fun waterMachineOps(machine: WaterMachine, moment: Moment){when(moment){Moment.EARLY_MORNING,Moment.DRINKING_WATER - machine.turnCooling()Moment.INSTANCE_NOODLES - machine.turnHeating()Moment.AFTER_WORK - machine.turnOff()}
}fun main() {val machine WaterMachine()waterMachineOps(machine, Moment.DRINKING_WATER)waterMachineOps(machine, Moment.INSTANCE_NOODLES)waterMachineOps(machine, Moment.DRINKING_WATER)waterMachineOps(machine, Moment.AFTER_WORK)
}执行结果
turn cooling
turn heating
turn cooling
turn off结构型模式
装饰者模式用接口委托减少样板代码
在 Java 中当我们要给一个类扩展行为的时候通常有两种选择
设计一个继承它的子类使用装饰者模式对该类进行装饰然后对功能进行扩展。
不是所有场合都适合采用继承的方式来满足类扩展的需求需遵循“里氏替换原则”所以很多时候装饰者模式成了我们解决此类问题更好的思路。
装饰者模式在不必改变原类文件和使用继承的情况下动态地扩展一个对象的功能。该模式通过创建一个包装对象来包裹真实的对象。
总结来说装饰者模式做的是以下几件事情
创建一个装饰类包含一个需要被装饰类的实例装饰类重写所有被装饰类的方法在装饰类中对需要增强的功能进行扩展。
可以发现装饰者模式很大的优势在于符合 “组合优于继承” 的设计原则规避了某些场景下继承所带来的问题。然而它有时候也会显得比较啰唆因为要重写所有的装饰对象方法所以可能存在大量的样板代码。
在 Kotlin 中我们可以利用 by 关键字委托特性将装饰类的所有方法委托给一个被装饰的类对象然后只需覆写需要装饰的方法即可让装饰者模式的实现变得更加优雅。
interface MacBook {fun getCost(): Intfun getDesc(): Stringfun getProdDate(): String
}
class MacBookPro: MacBook {override fun getCost() 10000override fun getDesc() Macbook Prooverride fun getProdDate() Late 2011
}
// 装饰类
class MacBookUpgrade(val macBook: MacBook) : MacBook by macBook {override fun getCost() macBook.getCost() 219override fun getDesc() macBook.getDesc() , 1G Memory
}fun main() {val macBookPro MacBookPro()val macBookUpgrade MacBookUpgrade(macBookPro)println(macBookUpgrade.getCost())println(macBookUpgrade.getDesc())
}如代码所示我们创建一个代表 MacBook Pro 的类它实现了MacBook的接口的3个方法分别表示它的预算、机型信息以及生产的年份。
当你觉得原装MacBook的内存配置不够的时候需要对其进行一下配置升级比如再加入 1G 的内存这时候配置信息和预算方法都会受到影响。
所以通过 Kotlin 的类委托语法 我们实现了一个MacBookUpgrade类该类会把MacBook接口所有的方法都委托给构造参数对象macbook。
因此我们只需通过覆写的语法来重写需要变更的cost和getDesc方法。由于生产年份是不会改变的所以不需重写MacBookUpgrade类会自动调用装饰对象的getProdDate方法。
总的来说Kotlin 通过类委托的方式减少了装饰者模式中的样板代码否则在不继承Macbook类的前提下我们得创建一个装饰类和被装饰类的公共父抽象类。
通过扩展函数代替装饰者
class Printer {fun drawLine() {println(————————)}fun drawDottedLine() {println(- - - - -)}fun drawStars() {println(********)}
}这里我们定义了一个Printer绘图类它有3个画图方法分别可以绘制实线、虚线及星号线。
现在我们有一个新的需求就是希望在每次绘图开始和结束后有一段文字说明来标记整个绘制的过程。
一种思路是对每个绘图的方法装饰新增的功能然而这肯定显得冗余尤其是未来Printer类可能新增其他的绘图方法这不是一种优雅的设计思路。
我们来看看如何用扩展来代替装饰类提供更好的解决方案
fun Printer.startDraw(decorated: Printer.() - Unit) {println( start drawing )this.decorated()println( end drawing )
}fun main() {Printer().run {startDraw { drawLine() }startDraw { drawDottedLine() }startDraw { drawStars() }}
}还记得之前介绍的run方法吗它接收一个lambda函数为参数以闭包形式返回返回值为最后一行的值或者指定的return的表达式。结合run的语法我们就可以比较优雅地实现我们的需求。
总结
设计模式Kotlin 中的解决方式备注工厂方法模式单例 object 类 invoke 重载伴生对象companion object invoke 重载伴生对象扩展方法创建型模式抽象工厂模式内联函数 inline reified创建型模式构建者模式具名可选参数 require 方法约束创建型模式观察者模式Delegates.Observable 委托语法Delegates.Vetoable 委托语法行为型模式策略模式高阶函数::函数引用语法行为型模式模板方法模式高阶函数::函数引用语法行为型模式迭代器模式运算符重载 iterator 扩展函数重载 iterator行为型模式责任链模式仿造偏函数行为型模式状态模式利用 ADT代数数据类型行为型模式装饰者模式接口委托 by 语法扩展函数结构型模式