使用简单继承的模式实现多个不同的鸭子实现类。
新需求 - 鸭子能飞
现在公司为了甩开其他竞争者,需要在应用中添加一个新需求,得让鸭子飞起来。
如图所示,只要在鸭子类中新增一个让鸭子飞的方法即可。
可怕的事情发生了
在公司的应用演示会上,有很多橡皮鸭在屏幕上飞来飞去。
造成这种问题提出现的原因:并不是所有的鸭子都会飞。
提示:
-
对代码所做的局部修改,影响层面可不只是局部(会飞的橡皮鸭)
-
当涉及到代码的维护时,为了“复用”代码而使用继承,结局并不完美
利用接口改造代码
把fly()
方法从超类中去取出来,放进一个Flyable
接口,只有会飞的鸭子去实现这个飞的接口。
这种接口改造,确实可以实现目的。但是太笨了,完全不利于代码的复用与维护。如果项目后期需要你修改一下鸭子的飞行动作,那么你是不是要去每一个鸭子的实现类里进行修改。
Java接口不具有现实代码,所以继承接口无法实现代码的复用。这意味着:无论什么时候你需要修改其实现类的某一个行为,你都要往下追踪到实现类中进行修改。
我们要做的应该是,花费较少的时间重做代码,而让程序去做更酷的事情。
把问题归零
上面问题出现的原因,就是新需求来的时候,我们改动了系统中的某些部分,导致系统其他部分受到了影响。
所以,要解决这个问题,就是要把这两部分分开,各自封装处理。
即:
设计原则:
找到应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
分开变化和不会变化的部分
找出应用中哪些是不变的,哪些是变化的。
不变的:swim()
、display()
基本上所有的鸭子都会
会变化的:fly()
、quack()
不同的鸭子叫声和飞行动作不一样
把这两部分分开
设计鸭子的行为
好的代码是非常有弹性的,就是因为一开始代码没有弹性,才走到现在这条路上的。
如果我们现在需要生产一个新的绿鸭头实例,并指定特定“类型”的飞行行为给它。干脆顺便让鸭子的行为可以动态地改变好了。换句话说,我们应该在鸭子类中包含设定行为的方法,这样就可以在“运行时”动态地“改变”绿鸭头的飞行行为。
要实现这个目标,就需要用到下面的设计原则:
针对接口编程,而不是针对实现编程。
-
以往的做法:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于“实现”,我们被实现绑定死死的,要更改行为,就要去实现类里进行代码的修改。
-
针对接口编程:Duck的子类将使用接口(
FlyBehavior
与QuackBehavior
)所表示行为,所以实际的“实现”不会被绑死在鸭子的子类中。(换句话说,特定的具体行为编写在实现了FlyBehavior
与QuackBehavior
的类中)。-
这样做的好处,后面新增一些新的行为,就不会影响到之前的代码,只需要在接口上进行扩展即可。而且这种方式,鸭子类就不再需要知道行为的具体实现细节,只需要调用接口的方法即可。
-
整合鸭子的行为
关键在于,鸭子现在会将飞行和呱呱叫行为“委托”别人处理,而不是使用定义在Duck类或其子类的呱呱叫和飞行行为。
每只鸭子都会引用实现QuackBehavior
接口对象,鸭子对象不亲自处理呱呱叫和飞行行为,而是委托给代理接口去处理。
封装行为的大局观
核心:不再把鸭子的行为说成是“一组行为”,现在开始把行为想成是“一族算法”。
“有一个”可能比“是一个”更好
每一个鸭子都有一个FlyBehavior
和一个QuackBehavior
,好将飞行和呱呱叫行为委托给它们代为处理。当你将两个类结合起来使用,这就叫组合。
这种组合做法与“继承”不同的地方在于,鸭子行为不是继承来的,而是和适当的行为对象“组合”来的。
这就是第三个设计原则:
多用组合,少用继承。
使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
总结
这就是策略模式(Strategy Pattern),策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。