从需求变更中学设计模式 -- 策略模式

334 阅读2分钟

原功能

  1. 在一个模拟鸭子游戏中, Duck 鸭子类是一个抽象类,叫法 quack() 和 游泳 swim() 有具体实现,外形 display() 方法抽象,由子类实现。
  2. MallardDuck和RedheadDuck都继承自父类Duck,有自己的display() 实现

需求变更

  • Day1: 让鸭子会飞
  • Day2: 有木头鸭加入
  • Day3: 鸭子飞的动力源不一样 ......

解决方案

1. Duck抽象类加 fly() 方法的实现

bug: 所有继承自Duck的子类都会获得 fly() 的能力

fix: 子类自行复写 fly() ,不需要该能力的复写为空

  • 优点:快速实现需求
  • 缺点:疲于修改子类

优化思路:让“某些”(而不是全部)鸭子类型可飞或可叫

2. 利用接口

FlyAble() 和 QuackAble() 两个接口,给具体 Duck 子类按需实现

  • 优点:不会给新继承自 Duck的子类带来bug
  • 缺点:功能无法复用

优化原则:封装变化

3. 应用策略模式

  1. 将可变的行为和抽出鸭子类:飞行和呱呱叫
  2. 飞行和呱呱叫都有自己的类提供具体的实现
  3. 鸭子类不需要知道具体的行为,只需要有该类型行为的接口引用 -- 应用的原则:针对接口编程
  4. 行为与鸭子类分开,具体的鸭子类获得怎样的行为不是由继承来的,而是通过设置行为的具体类获得行为 -- 应用的原则:多用组合,少用继承

优点:

  1. 飞行和呱呱叫的行为独立于鸭子类,可以被其他复用
  2. 新增行为时,不会影响已有的用户

总结

原则

  1. 封装变化
  2. 多用组合,少用继承
  3. 针对接口编程,不针对实现编程

策略模式定义

  1. 定义并分别封装算法族
  2. 算法族直接可以相互替换
  3. 让算法独立与使用算法的用户

Tips

  1. 原则与模式可以用于开发的每个阶段
  2. 达到优化的目的就不要做额外的变动(如将鸭子类做成接口,将swim() 方法抽成另一个算法族),以免引入新的bug
  3. 一般情况下,可以有相应的模式可以套用到具体的功能实现中
  4. 如果没有找到对应的模式,则根据原则

使用模式的好处

  1. 避免陷入实现琐碎细节的纠结
  2. 利用模式可以有更多的时间用在业务逻辑的优化上
  3. 形成一个模式社区,形成一种模式思维