重修设计模式-行为型-策略模式
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。
策略模式(Strategy Pattern)的定义比较简单,主要是一个策略接口和一组实现这个接口的策略类。基于“面向对象的多态特性”和“面向接口编程”的原则,在运行时根据场景灵活选择具体策略。
策略模式的主要的作用是解耦策略的定义、创建和使用,控制代码的复杂度,让每个部分都不至于过于复杂、代码量过多。对于复杂代码来说,策略模式还能让其满足开闭原则,添加新策略的时候,最小化、集中化代码改动,减少引入 bug 的风险。
策略模式的主要角色
策略接口(Strategy)
:定义算法的公共接口,所有的具体策略类都要实现这个接口。接口中声明了客户端会用到的所有方法。具体策略类(ConcreteStrategy)
:实现了策略接口,提供了具体的算法实现。上下文类(Context)
:持有一个策略接口的引用,在运行时可以动态地设置具体的策略对象,并调用其方法。
使用策略模式时,首先要定义出策略接口和一组具体实现,然后根据 type 确定具体使用哪个策略,也就是策略的创建。创建策略时一般通过工厂方法模式,将创建逻辑独立。策略模式的通用代码如下:
//1.定义策略接口和策略的具体实现
//抽象的策略接口
interface Strategy {fun doSomething()
}//具体策略实现1
class ConcreteStrategy1 : Strategy {override fun doSomething() {}
}//具体策略实现2
class ConcreteStrategy2 : Strategy {override fun doSomething() {}
}//2.通过工厂进行具体策略的创建
//策略工厂
object StrategyFactory {fun create(type: Int):Strategy {return when (type) {1 -> ConcreteStrategy1()2 -> ConcreteStrategy2()else -> throw Throwable("strategy type no support!")}}
}//3.客户端调用(上下文角色,其实也就是客户端调用处)
fun main() {//通过类型获取策略,type一般是运行时确定,比如用户输入、接口请求结果等不确定因素val type = 1val strategy = StrategyFactory.create(type)strategy.doSomething()
}
定义比较抽象,还是举个具体的例子,来熟悉使用策略模式的思考过程和具体实现。
一个简单的支付系统,支持微信和支付宝支付,支付渠道根据用户选择确定。
首先不使用设计模式,直接根据需求直接将代码实现:
enum class PaymentWay {Alipay, WeChat
}class ShoppingPayment(val amount: Double) {fun pay(paymentWay: PaymentWay) {when (paymentWay) {PaymentWay.Alipay -> {payWithAlipay()}PaymentWay.WeChat -> {payWithWechat()}}}private fun payWithAlipay() {println("通过支付宝支付:${amount}")}private fun payWithWechat() {println("通过微信支付:${amount}")}
}//调用处:
fun main() {val shoppingPayment = ShoppingPayment(100.00)shoppingPayment.pay(PaymentWay.Alipay)
}
如果需求止步于此,这样写当然没问题。但实际上,需求是不断迭代的,后续如果增加信用卡支付,又需要将信用卡支付代码添加到ShoppingPayment 类中,让其代码量越来越大;况且每种支付方式的实现有很大不同,比如如何签名、如何回调结果等,这些代码都糅杂在一个类中,会造成类的责任过多,复用困难,对代码的可读性、可维护性都有影响。
对于一个臃肿的类进行优化,思路就是将类中的某些代码拆分出来,独立成职责更加单一的小类,并通过合理的方式组装起来,实际上这也是设计模式所做的事。下面使用策略模式将上述代码优化:
//1.定义支付策略
interface PaymentStrategy {fun pay(amount: Double)
}//2.具体支付实现
class AlipayPayment : PaymentStrategy {override fun pay(amount: Double) {println("通过支付宝支付:${amount}")}
}class WechatPayment : PaymentStrategy {override fun pay(amount: Double) {println("通过微信支付:${amount}")}
}//3.是用策略的上下文
class ShoppingPaymentV2(val amount: Double) {fun pay(paymentWay: PaymentWay) {val paymentStrategy = PaymentFactory.create(PaymentWay.Alipay)paymentStrategy.pay(amount)}
}//4.策略创建工厂
class PaymentFactory {companion object {@JvmStaticfun create(paymentWay: PaymentWay): PaymentStrategy {return when (paymentWay) {PaymentWay.Alipay -> AlipayPayment()PaymentWay.WeChat -> WechatPayment()}}}
}//5.调用处
fun main() {val shoppingPayment2 = ShoppingPaymentV2(100.0)shoppingPayment2.pay(PaymentWay.Alipay)
}
封装之后具体支付的实现代码再多也不怕了,因为类的职责非常单一,只包含一种支付的所有实现,即使改动影响范围也有限,代码复用也更方便了。
策略的创建是由工厂来完成,屏蔽了策略创建的细节。对于策略创建的工厂,还可以根据工厂方法的变种来优化,比如缓存无状态的策略方便复用;通过查表法避免 if/else 分支等。
总结
策略模式的优点是算法切换灵活,符合开闭原则和单一职责原则,提高代码可读性和可维护性。但设计模式的适用场景是最重要的,具体场景具体实现。遵循 KISS 原则,怎么简单怎么来,就是最好的设计。非得用策略模式,搞出 n 多类,反倒是一种过度设计。