原功能
- 在一个模拟鸭子游戏中, Duck 鸭子类是一个抽象类,叫法 quack() 和 游泳 swim() 有具体实现,外形 display() 方法抽象,由子类实现。
- MallardDuck和RedheadDuck都继承自父类Duck,有自己的display() 实现
需求变更
- Day1: 让鸭子会飞
- Day2: 有木头鸭加入
- Day3: 鸭子飞的动力源不一样 ......
解决方案
1. Duck抽象类加 fly() 方法的实现
bug: 所有继承自Duck的子类都会获得 fly() 的能力
fix: 子类自行复写 fly() ,不需要该能力的复写为空
- 优点:快速实现需求
- 缺点:疲于修改子类
优化思路:让“某些”(而不是全部)鸭子类型可飞或可叫
2. 利用接口
FlyAble() 和 QuackAble() 两个接口,给具体 Duck 子类按需实现
- 优点:不会给新继承自 Duck的子类带来bug
- 缺点:功能无法复用
优化原则:封装变化
3. 应用策略模式
- 将可变的行为和抽出鸭子类:飞行和呱呱叫
- 飞行和呱呱叫都有自己的类提供具体的实现
- 鸭子类不需要知道具体的行为,只需要有该类型行为的接口引用 -- 应用的原则:针对接口编程
- 行为与鸭子类分开,具体的鸭子类获得怎样的行为不是由继承来的,而是通过设置行为的具体类获得行为 -- 应用的原则:多用组合,少用继承
优点:
- 飞行和呱呱叫的行为独立于鸭子类,可以被其他复用
- 新增行为时,不会影响已有的用户
总结
原则
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
策略模式定义
- 定义并分别封装算法族
- 算法族直接可以相互替换
- 让算法独立与使用算法的用户
Tips
- 原则与模式可以用于开发的每个阶段
- 达到优化的目的就不要做额外的变动(如将鸭子类做成接口,将swim() 方法抽成另一个算法族),以免引入新的bug
- 一般情况下,可以有相应的模式可以套用到具体的功能实现中
- 如果没有找到对应的模式,则根据原则
使用模式的好处
- 避免陷入实现琐碎细节的纠结
- 利用模式可以有更多的时间用在业务逻辑的优化上
- 形成一个模式社区,形成一种模式思维