利用高阶函数简化策略模式、模板方法模式

策略模式、模板方法模式解决的问题比较类似,并且都可以依靠Kotlin中的高阶函数特性进行改良。

遵循开闭原则:策略模式

假设有一个表示游泳运动员的抽象类Swimmer,有一个游泳的方法swim:

class Swimmer {

fun swim() {
println("I am swimming...")
}
}

//invoke
>>> Swimmer().swim()
I am swimming...

由于这位运动员在游泳方面很有天赋,他很快掌握了蛙泳、仰泳、自由泳多种姿势。所以我将对Swimmer进行改造:

class Swimmer {
fun breaststroke() {
println("I am breaststroke...")
}
fun backstroke() {
println("I am backstroke...")
}
fun freestyle() {
println("I am freestyling...")
}
}

然而这并不是一个很好的设计。首先,并不是所有的游泳运动员都掌握了这3种游泳姿势,如果每个Swimmer类对象都可以调用所有方法,显得比较危险。其次,后续难免会有新的行为方法加入,通过修改Swimmer类的方式违背了开放封闭原则(open for extension,closed for modification。面向扩展开放,面向修改关闭)。

所以呢,更好的做法是将游泳这个行为封装成接口,根据不同的场景我们可以调用不同的游泳方法。比如这位游泳运动员计划周末游自由泳,其他时间则游蛙泳。策略模式就是一种解决这种场景很好的思路。

策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的用户

本质上,策略模式做的事情就是将不同的行为策略(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(val strategy: SwimStrategy) {
fun swim() {
strategy.swim()
}
}

//invoke
Swimmer(Freestyle()).swim()
Swimmer(Breaststroke()).swim()

这个方案实现了解耦和复用的目的,且很好实现了在不同场景切换采用不同的策略。然而,该版本的代码量也比之前多了很多。

高阶函数抽象算法

如果用高阶函数的思路来重新思考下策略类,显然将策略封装成一个函数然后作为参数传递给Swimmer类会更加的简洁。由于策略类的目的非常明确,仅仅是针对行为算法的一种抽象,所以高阶函数式是一种很好的替代思路。

fun breaststroke() { ...}

fun Backstroke() { ...}
fun freestyle(){ ...}

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

//invoke
Swimmer(::freestyle).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方法是一个抽象方法。接下来我们再定义具体的子类来继承CivicCenter-Task类,然后对抽象的步骤进行实现。

class PullSocialSecurity: CivicCenterTask {

override fun askForHelp() {
println("ask for pulling teh social security")
}
}

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

//invoke
>>> PullSocialSecurity().execute()
line up to take a number
ask for pulling the social security
evaluation service attitude

>>> ApplyForCitizenCard().execute()
line up to take a number
apply for a citizen card
evaluaten service attitude

不出意料,两者的步骤2)的执行结果是不一样的。

不得不说,模板方法模式的代码复用性已经非常高了,但是我们还是得根据不同的业务场景都定义一个具体的子类。幸运的是,在Kotlin中我们同样可以用改造策略模式的类似思想,来简化模板方法模式。依靠高阶函数,我们可以在只需一个CivicCenterTask类的情况下,代替继承实现相同的效果。

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

fun pullSocialSecurity() { ... }
fun applyForCitizenCard() { ... }
}

//invoke
>>> CivicCenterTask().execute(::pullSocialSecurity)
live up to take a number
ask for pulling the social security
evaluaten service attitude

>>> CivicCenterTask().excute(::applyForCitizenCard)
line up to take number
apply for a citizen card
evaluaten service attitude

如你所见,在高阶函数的帮助下,我们可以更加轻松地实现模板方法模式。

作者

8MilesRD

发布于

2020-01-17

更新于

2020-01-19

许可协议

评论