浅入浅出设计模式
简介
GoF(“四人帮”Gang of Four,指Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides四人)的《设计模式》(1995年出版)是第一次将设计模式提升到理论高度,并将之规范化。本书提出了23种基本设计模式,自此,在可复用面向对象软件的发展过程中,新的大量的设计模式不断出现。
1.从前有一群鸭子
在遥远的丹麦生活着一群快乐的鸭子,它们会唱歌(quack),跳舞(swim)还会表现自己(display)。我们来把这群鸭子设计成一个类。UML图如下所示
就像我们看到的,每只鸭子都会同样的行为。
2.有鸭自远方来
后来的一天,从非洲来了一群“南非鸭”,它们和丹麦鸭一样会唱歌跳舞,不过表现自己就和丹麦鸭的表现不同了。
那这里我们就来用抽象类的方式来解决这个问题,如下图所示(抽象用斜体表示)
这样,我们通过一个抽象类Duck来实现quack和swim方法的复用,而不同的地方display由继承的子类分别实现。
要点1:找出应用中可能变化之处,把它们独立出来,不要和那些不需要变化的代码混合在一起。
3.每只鸭子上辈子都是折翼的天使
那是一个电闪雷鸣的日子,鸭老大给小鸭们讲了它们祖先天鹅翱翔于蓝天的故事。原来,它们上辈子都是折翼的天使……
于是,这群鸭子展开翅膀~~ 灰了起来 - -||
到了这里,鸭子们需要一个飞(fly)的方法,既然大家都会飞,就加到父类Duck里吧。
但是,有的鸭子(玩具鸭,姑且算鸭子吧)不会飞啊~~ 那该怎么办呢?我们的设计是不是出现了错误?
嗯~ 我们先覆盖父类中的方法来实现我们的需求吧。
OK,我们实现了需求
要点2:为了"复用"(reuse)目的而使用继承,貌似不是很好
仔细思考:
1、我们是不是把这个问题解决了?
2、如果又来了个A鸭子(不会叫,不会有用,不会飞);或者是B鸭(会汪汪叫,会游泳,不会飞)等等,我们该怎么办?
3、利用继承来提供Duck的行为,这会导致下列哪些缺点?
a.代码在多个子类中重复
b.运行时的行为不容易改变
c.我们不能让鸭子跳舞
d.很难知道所有鸭子的全部行为
e.鸭子不恩那个同时又飞又叫
f.会造成其他鸭子不想要的改变
4.你是否想到了"接口"
看起来不错的样子~
如果子鸭子类特别多,问题就出来了,那岂不是每种鸭子都各自实现?无法实现代码复用的目的?
1、现在我们知道使用继承并不能很好的解决问题,因为鸭子的行为在子类里不断的改变,并且让所有的子类都有这些行为是不恰当的。
2、Flyable和Quackable接口一开始似乎不错,解决了问题(只有会飞的鸭子才继承Flyable),但是接口不具有实现代码,所以继承接口无法达到代码的复用。
要点3:多用组合,少用继承
5.重新设计鸭子
Ⅰ.分开变化和不会变化的部分
(本图截取自深入浅出设计模式)
Ⅱ.针对接口编程 (题外话)
我们利用接口代表每个行为,比方说IFlyBehavior和IQuackBehaviour,而行为的每个实现都将实现其中的一个接口。
这种方法和以往不同的地方在于,以前的做法是:行为来自Duck父类的具体实现,或者是继承某个接口并由某个接口来自行实现而来。这两种做法都是依赖于“实现”, 我们被实现绑住,没办 法更改行为(除非写更 多的代码)
举例说明:
"针对实现编程"
Dog d=new Dog();
d.bark();
"针对接口/超类型编程"
Animal animal=new Dog();
animal.bark();
更好的是,子类实例化的动作不再需要在代码中硬编码,例如
new Dog();而是"在运行时才指定具体实现的对象"
a=getAnimal();
a.makeSound();
Ⅲ.实现鸭子的行为
Ⅳ. 整合鸭子的行为
注意,上图中的Duck的两个成员是接口类型,即我们上面提到的IFlyBehavior 和IQuackBehavior 接口类型
别忘记,因为yellowduck继承了duck的,所以具有flyBehavior和quackBehavior实例变量
Ⅴ. 测试代码
④输入并编译测试类
public class MiniDuck()
{
public static void main(string[] args)
{
Duck mini=new YellowDuck();
mini.performFly();
mini.performQuack();
}
}
⑤运行代码
6.总结
上图是整个重新设计后的类图
我们这里描述事情的方式也少有改变。不再把鸭子的行为说成是“一组行为”。我们开始把行为想成是“一组算法”。
而上图就是GoF的设计模式中的策略模式
策略模式:定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户