作者:折纸
个人博客:https://www.zhezhi.press
记录菜鸡的成长之旅!
1 从一堆鸭子说起
众所周知《机器学习》(周志华版)被称为西瓜书,我觉得《Head First 设计模式》也可以被称为鸭子书~ 假设有这么一个模拟鸭子游戏SimUDuck,游戏中会出现各种鸭子,它们一边游泳一边呱呱叫。于是有这么一个鸭子超类,并且让各种鸭子继承这个超类;
这看起来似乎还不错,所有的鸭子都会继承Duck超类,并且只需要重写display方法以打印自己的特征就可以。但需求是在变化的。
2 让鸭子飞起来!
好吧,看起来不难,我们只需要给Duck类加一个fly()方法并实现就可以了;确实这样一来所有继承Duck类的鸭子都会飞了,但是我们忽略了一个事实——不同子类之间的差异性,橡皮鸭子原本不会飞现在飞起来了!
对代码的局部修改 影响的不一定只停留在局部;当涉及“维护”时,为了“复用”目的而使用继承,结局并不完美
我的理解:对超类的修改要谨慎,避免造成不必要的麻烦!如无必要,勿增实体
2.1 解决办法:子类重写
我们可以重写不会飞的橡皮鸭子的fly方法,将其改为什么也不做就可以了。
这确实解决了橡皮鸭可以飞的问题,但是以后每次设计新的子类的时候,我们都需要检查fly、quack、swim等方法,这就失去了复用的意义。
通过继承提供Duck的行为,可能会带来的缺点
代码在多个子类中重复(需要改写行为以适应不同子类)
运行时的行为不容易改变(无法灵活切换)
很难知道所有鸭子的全部行为
改变会牵一发而动全身,导致其他鸭子不想要的改变
2.2 解决办法:通过接口
接口是对行为的抽象,可以把fly()、quack()从超类中提取出来,放进Flyable、Quackable接口,然后只有会飞、会叫的鸭子才回去实现这个接口。
但这一来重复的代码将变得更多,对于每一种会飞、会叫的鸭子都需要去实现对应的接口,此外还有不同飞行方法、不同叫声的叫法,这使得代码的可复用性降得很低。
2.3 重新审视问题
首先我们知道在这个案例中,fly是新加入的需求~
并且使用继承并不能很好地解决问题,因为鸭子的行为在子类里不断地改变,并且让所有的子类都有这些行为是不恰当的。Flyable与Quackable接口一开始似乎还挺不错,解决了问题(只有会飞的鸭子才继承Flyable),但是Java接口不具有实现代码,所以继承接口无法达到代码的复用。
这意味着: 无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中修改它,一不小心,可能会造成新的错误!
幸运的是,有一个设计原则,恰好适用于此状况。
设计原则: 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
下面是这个原则的另一种思考方式:“把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分”。-->系统变得更有弹性
这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。
好,该是把鸭子的行为从Duck类中取出的时候了!
3 抽离易于变化的部分
一种方法是建立2组完全远离Duck类的Class,每一组类将实现fly、或者是quack,而类中有对于fly的不同实现;而且可以使得鸭子的行为可以动态地改变(setter)
设计原则:针对接口编程,而不是针对实现编程。
我们利用接口代表每个行为,行为的每个实现都将实现其中的一个接口。这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。
这两种做法都是依赖于“实现”,我们被实现绑得死死的,没办法更改行为(除非写更多代码)。在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中(鸭子不会负责实现Flyable、Quackable接口)。(换句话说,特定的具体行为编写在实现了FlyBehavior与QuakcBehavior的类中,从而实现了解耦)。
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了;并且我们也可以新增一些行为(不同的实现),不会影响到已有的行为类和“使用”其他已有行为的鸭子类。
这么一来,有了继承的“复用”好处,却没有继承所带来的包袱。
4 复盘
你也许可能听过Has-A is Better than is-A
的说法,也许你可能像我一样此前并没有很直接地感受到或者思考过Why Has-A is Better than is-A
?
但通过鸭子的例子我想你应该有一些感受了,is-A也就是继承的关系(不同的鸭子继承超类Duck),而has-A更偏向于组合的关系(鸭子类有FlyBehavior类和QuackBehavior类,鸭子是容器/对象,而后两者是容器/对象的组件类)
设计原则:多用组合,少用继承
如你所见,使用组合建立系统具有很大的弹性,不仅可将算法族(对于同一个行为的不同实现方法)封装成类,更可以“在运行时动态地改变行为”(setter方法),只要组合的行为对象符合正确的接口标准即可。
组合用在许多设计模式中,它也有许多优点和缺点,在之后的学习中我们还会继续讨论;
5 策略模式
策略模式是一种行为型设计模式, 它可以在运行时选择算法;与直接实现某个算法不同,代码在运行时可以选择在算法族中选取一个执行。
(In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.wikipedia)
或者说
策略模式将某一个行为(接口)通过不同的方法/算法实现[上文所提的算法族],方便需要实现该接口对应行为[Has-A]的程序/客户可以在"运行时"灵活切换该行为实现的方法/策略,使得策略的变化独立于使用它的客户/程序;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。