策略模式、模板方法模式解决的问题比较类似,并且都可以依靠Kotlin中的高阶函数特性进行改良。
遵循开闭原则:策略模式 假设有一个表示游泳运动员的抽象类Swimmer,有一个游泳的方法swim:
class Swimmer { fun swim () { println("I am swimming..." ) } } >>> 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() } } Swimmer(Freestyle()).swim() Swimmer(Breaststroke()).swim()
这个方案实现了解耦和复用的目的,且很好实现了在不同场景切换采用不同的策略。然而,该版本的代码量也比之前多了很多。
高阶函数抽象算法 如果用高阶函数的思路来重新思考下策略类,显然将策略封装成一个函数然后作为参数传递给Swimmer类会更加的简洁。由于策略类的目的非常明确,仅仅是针对行为算法的一种抽象,所以高阶函数式是一种很好的替代思路。
fun breaststroke () { ...}fun Backstroke () { ...}fun freestyle () { ...}class Swimmer (val swimming: () -> Unit ) { fun swim () { swimming() } } 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" ) } } >>> 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 () { ... } } >>> 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
如你所见,在高阶函数的帮助下,我们可以更加轻松地实现模板方法模式。