一、引言
1.1 先从简单的模拟鸭子应用做起
假设有一个模拟鸭子的游戏SimUDuck,游戏中有各种鸭子,一边游泳,一边呱呱叫。
1.2 现在我们得让一些鸭子能飞
- 实现1:新增fly()
在抽象父类中新增fly()方法并实现, 使得所有的鸭子都具有了fly()。
问题:并不是所有鸭子都能飞。
总结:当维护的时候,为了复用而使用继承,实际效果并不好
- 实现2:fly()空实现
抽象父类的fly()方法进行空实现,具体实现由子类自己去做。
问题: 基本上每出现一个鸭子类型,都要去检测其行为与默认实现之间的关系。可能新出现的鸭子品类的quack(), swim()和fly()等行为与父类中实现的完全不一样。
总结:使用继承的时候,共性抽取到父类一定要小心处理,只有绝对不变的共性才适合抽取到父类中。
- 实现3:利用接口
使用接口,将不是所有鸭子都一定会存在的行为定义到各自的接口中,具体的鸭子实现该接口就好了。
问题:可能多中鸭子的某个行为实现是一样的,通过接口无法实现代码复用。
二、把为题归零
现在我们知道使用继承并不能很好的解决问题,因为鸭子的行为在子类里不断变化,并且并不是所有子类都应该具有这些行为。使用Flyable、Quackable接口的方式,看似解决了问题(只有会飞的鸭子才要实现Flyable接口)。但是Java接口没有实现代码,所有继承接口无法达到代码的复用。
设计原则:封装变化
找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起。
2.1 分来变化和不变化的部分
我们知道Duck中的fly()和quack()会随着鸭子的不同而改变。为了要把2个行为从Duck类中分开,我们要把它们从Duck中取出来,建立一组新类代替每个行为。
2.2 设计鸭子的行为
从现在开始,鸭子的行为将被放到分开的类中,此类专门提供某行为接口的实现。这样,鸭子类就不需要知道行为的实现细节。
设计原则
针对接口编程,而不是针对实现编程。
2.3 实现鸭子的行为
在此,我们有2个接口,FlyBehavior和QuackBehavior,还有它们对应的类,负责实现具体的行为。
优点:新增一些新的行为,不会影响到既有的行为类,也不会影响“使用”某个行为的鸭子类。
2.4 整合鸭子的行为
关键在于,鸭子现在将飞行和呱呱叫的动作“委托”别人处理,而不是使用定义在Duck类中的呱呱叫和飞行方法。
-
在Duck类中加入两个实例变量(接口类型)flyBehavior和quackBehavior。
-
在Duck类中实现performFly()和performQuack()方法
-
在具体鸭子子类的构造方法红设定具体的flyBehavior和quackBehavior
2.5 封装行为的大局观
由于鸭子的fly和quack行为是变化的,所以需要将这些行为进行分装。为了能统一管理所有同类行为,因此使用了接口。
设计原则
多用组合,少用继承。
策略模式定义了算法族,分别分装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户
源码:github.com/chentianmin…
strategy包!