前言
作为一个开发者,在工作中最经常遇到也最怕遇到的情况就是产品过来变更需求。一旦产品变更需求,来来回回的调整所带来的心里烦躁不说,有些变更常常会导致代码结构需要大调整才能实现效果。那么要如何应对这种需求变更导致经常大范围调整代码呢?可以考虑考虑策略模式。
策略模式
又称“政策模式”。定义算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
上述的定义引用自《Head First设计模式》是不是感觉不明觉厉。可以这么理解就是找出需求中会变化的与不会变化的内容,将会变化通过统一的接口定义一个算法家族,将需求中会变化的部分与不变的部分分开处理。
结构
看那个干巴巴的定义,我相信还是感觉一脸懵逼,这是什么鬼?我们来看看类图,宏观的看看策略模式的全貌。
变化部分:通过统一接口IStrategyA,IStrategyB分组来封装变化部分,可以分别理解为一个算法族。
不变部分:通过组合的方式将IStrategyA,IStrategyB两个接口组合进Client中。Client可以不用关心具体的算法内容是什么,直接调用接口方法即可。
实战
需求:假设要做一个模拟鸭子的游戏,鸭子可以飞可以叫。
面对这个需求直观的感受是我们可以写一个Duck超类里面定义一些鸭子的一些行为,然后不同的鸭子都去继承它。如下图:
到这里貌似这个需求已经完成,但是这时如果产品希望让某些鸭子会飞,你可能会想到这么做:直接在超类Duck里面加入fly()就可以满足:
但是有没有发现这个时候RuberDuck一个模型鸭也会飞了。这时你肯定会说我只要在RuberDuck中去复写fly(),不就好了。但是这时候如果有几十种鸭子,每种鸭子的飞行行为都不一样,你就需要一一的去复写fly(),这时候就会感觉到心累了。
面对这样子的需求,首先我们可以发现鸭子会有各种飞行方式,会有各种叫声,很明显这将会是一个产品将来会频繁变更的点。根据上面关于策略模式的定义我们可以封装出两个算法族,一个是鸭子的飞行行为FlyBehavior,一个是鸭子的叫声方式QuackBehavior。如图:
有了鸭子两种行为,那我们现在还差个鸭子自己本身:
看上图你会发现
- 在Duck类中加入了“FlyBehavior”和“QuackBehavior”两个接口类型对象。(针对接口口编程)
- 开放了setFlyBehavior,setQuackBehavior两个接口可以动态改变行为方式。(算法族之间可以相互替换)
- 在performFly,performQuack中将行为委托给flyBehavior,quackBehavior两个引用对象,也不用关心是怎么飞,怎么叫的。
如此鸭子的行为与鸭子本身就解耦了,不管后续是新增火箭飞行,还是新增吱吱声等等各种行为,我们都只要实现FlyBehavior,QuackBehavior来新增行为即可满足需求,不需要调整任何鸭子的代码。假设MallaerDuck想在运行时修改飞行方式,可以直接通过setFlyBehavior动态修改行为,实现“运行时动态改变行为”。
OO设计原则
细心的同学可能就会发现上面的设计中遵守了好多的设计原则。分别如下:
- 封装变化:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 多用组合,少用继承:使用组合建立系统可以有很大的弹性,同时还可以做到“运行时动态改变行为”,只要组合的行为对象符合正确的接口标准即可。
- 针对接口编程,不针对实现编程:使用接口我们可以充分使用多态特性,提升框架的可扩展性。框架不需要关心具体的实现,只要符合接口标准就好,后续新增新的实现不需要调整框架,但框架却可以拥有新的实现能力。
下面我们来看看上述的例子是如何遵守设计原则的:
- 先从宏观的角度上,看我们将变化部分单独抽离出来遵守的是封装变化原则。
- 从鸭子的设计上看,我们在鸭子组合了鸭子的各种行为遵守的是多用组合,少用继承。
- 从鸭子组合方式上看,鸭子中组合的是FlyBehavior,QuackBehavior两个接口行为,而不是直接组合了具体的FlyWithWing,鸭子只管调用接口的方法,而不需要关心我这里调用的是哪个具体的实现类,这里遵守的是针对接口编程,不针对实现编程。
使用场景
当有如下场景的时候可以考虑使用策略模式:
- 当一个类定义了多种可变化的行为,并且这个类的操作中使用了很多的if/else, 或者switch/case等各种条件语句。那么就可以将相关的条件分支抽象到各自的策略算法中去,用策略算法来代替条件语句。
- 客户不需要关心具体的行为内容,那么可以使用策略模式封装避免暴漏复杂的行为。
- 客户需要使用一个算法不同变体的时候可以考虑用策略模式。
- 几个相关的类只是具体的行为不一样,其他内容是一样的情况。“策略”提供了将多个行为分别设计成一个行为一个类,并且在客户眼里这些行为可以相互替换。
优缺点
优点:
- 可以消除一些条件判断语句。
- 让客户动态变化行为:同一算法下的行为可以相互替换。
- 提供一种替换继承的方法:通过组合来增加行为,而不是通过继承的方式来增加行为。
- 让客户与策略解耦:策略可以无限扩展,而不会影响到客户
缺点:
- 增加了代码中类的数量:毕竟每个策略都是一个类
总结
总而言之,策略模式可以做到将变化与不变的部分剥离开,减少代码之间的耦合,面对扩展的能力更强。在平常的开发过程中可以根据使用场景,如何有符合的时机就可以多进行实践,运用过程中可不能忘了这当中的弊端哦。
个人公众号:基石分享社
欢迎关注我的公众号,该公众号会持续输出Android开发过程中的各种基础知识的文章,共同学习成长。