定义
Strategy 策略模式
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。
动机
-
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担。
-
如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
案例
有这样一个案例,有一个销售单,需要计算汇率,但是不同国家的税法是不一样的,所以税费也不一样。所以我们根据需求可以这样做:
方案一
swift
enum RegionType {
case CN, US, DE, FR
}
class SalesOrder {
var taxType: RegionType
init(taxType: RegionType) {
self.taxType = taxType
}
func calculateTax() {
if (taxType == .CN) {
// CN***********
} else if (taxType == .US) {
// US***********
} else if (taxType == .DE) {
// DE***********
} else if (taxType == .FR) {
// FR***********
}
}
}
这种写法有什么不好呢?如果RegionType
增加一种,那么需要修改calculateTax
方法里的判断语句,这里因为代码简单看着没有什么,但是如果代码复杂的话,那么基本上需要在所有用到RegionType
枚举值的地方都需要修改代码。
还有一个缺点,一般发行一个APP在哪个国家是确定的,比如说这个APP是在中国发行的,那为什么要把其他国家的税法编译进来,不就是增大包体积了么?
所以我们看改进后的代码
方案二
swift
class Context {
// 表示一些上下文
}
protocol TaxStrategy {
func calculate(context: Context) -> Double
}
class CNTax: TaxStrategy {
func calculate(context: Context) -> Double {
// calculate CN tax
return 0
}
}
class USTax: TaxStrategy {
func calculate(context: Context) -> Double {
// calculate US tax
return 0
}
}
class DETax: TaxStrategy {
func calculate(context: Context) -> Double {
// calculate DE tax
return 0
}
}
class SalesOrder {
private var taxStrategy: TaxStrategy
init(taxStrategy: TaxStrategy) {
self.taxStrategy = taxStrategy
}
func calculateTax() {
func calculate() -> Double {
let context = Context()
return taxStrategy.calculate(context: context)
}
}
}
在方案二中,如果我们想新增国家法国,那么我们只要新增一个FRTax
类就行了:
swift
class FRTax: TaxStrategy {
func calculate(context: Context) -> Double {
// calculate FR tax
return 0
}
}
这个设计模式经典的遵守了开闭原则,只对扩展开发,对修改封闭。
还有上面说的包体积的问题,我们可以通过宏定义或者指定文件编译(不同类应该是要写在不同的文件里的),就可以在不同国家的APP里编译不同的代码逻辑了。
要点总结
-
Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
-
Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式(并不绝对)。
-
如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。