一、策略模式(Strategy Pattern)
设计一个父类Duck,定义并实现了fly()和quack()方法,定义abstract方法Display。子类MallardDuck和RedheadDuck继承Duck,并实现各自的Display方法。如下图:
1 继承的缺点
step 1:增加子类RubberDuck(橡皮鸭),继承Duck。如下图:
于是问题1出现了:橡皮鸭会fly(),会quack()(嘎嘎叫)。---------------------------
继承缺点1:父类的非abstract方法,会作用于所有子类。实现代码重用性的同时,造成了极大的不灵活,甚至错误。
step 2:在RubberDuck中,覆盖fly()和quack(),使之不会飞,叫声为squeak(吱吱叫)。如下图:
出现问题2:我想增加DecoyDuck(),诱饵鸭,诱饵鸭是木头假鸭,不会飞也不会叫。于是又得覆盖fly方法,内容完全与RubberDuck的fly方法一样,代码重复;再覆盖quack,不叫。如果我想在增加动力鸭呢,不会叫但会飞。。。。。。可怕的代码重复。
继承缺点2:如对父类的方法覆盖来实现个性化,造成代码在各子类中重复。
继承缺点3:无尽的覆盖,导致代码一致性差,很难知道所有鸭子的全部行为。
step 3:我想增加一头贪吃鸭,平时不叫,想吃东西时叫。出现问题3:oh,如何动态改变鸭子的行为?
继承缺点4:在运行时很难改变行为。
2 利用接口如何?
把fly和quack从Duck父类中剥离出来,分别放进flyable和quackable接口。这样一来,只有会飞的鸭子来实现flyable接口,会叫的鸭子来实现quackable接口。如下图:
这真是一个笨主意,重复的代码更多。因为每个会飞或会叫的鸭子子类,都得去实现一遍flyable或quackable方法。
3 解决之道
设计原则一:找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
fly和quack行为,会随鸭子的不同而改变。所以我们将把他们从Duck类中取出来,建立一组新类来代表每个行为。
设计原则二:针对接口编程,而不是针对实现编程。
我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。鸭子类不负责实现flyable和quackable接口,而是由我们制造一组其他类专门来实现FlyBehavior和QuackBehavior,这就称为“行为”类。这样,我们就把鸭子和行为分离开来,而以前的做法是:行为来自与父类的具体实现,或是子类的覆盖实现。这两种做法都是依赖于“实现”,我们想描述的的鸭子,被实现绑得死死的。
实质上,针对接口编程即是舍弃继承,使用组合。即是把各个行为的具体实现(行为为可变部分)组合成一种特性,赋给对应的Duck。如把FlyBehavior的不会飞的实现,和QuackBehavior的squeak(吱吱叫)实现,组合起来,和Duck父类和在一起,表示橡皮鸭。
于是,我们整合鸭子的行为如下:
Duck |
FlyBehavior flyBehavior QuackBehavior quackBehavoior |
performQuack() performFly() swim() display() |
public class Duck{
QuackBehavior quackBehavior;
//还有更多
public void performQuack(){
quackBehavior.quack();
}
}
public class MallardDuck extends Duck{
public MallardDuck(){
quackBehavior = new quack();
flayBehavior = new FlyWithWings();
}
public void display(){
System.out.println(“I’m a real Mallard duck”);
}
}
4 动态设定鸭子行为,增加2个方法:
Duck |
FlyBehavior flyBehavior QuackBehavior quackBehavoior |
performQuack() performFly() swim() display() setFlyBehavior() setQuackBehavior() |
public void setFlyBehavior(FlyBehavoir fb){
flyBehavior = fb;
}
setQuackBehavior()类似。
5 封装行为的大局观:
我们把可变的一组行为,如Quack,叫做一族算法,加以封装。鸭子可以不关注算法的具体实现,只是委托算法去做某种行为即可。如委托Quack算法去吱吱叫。
鸭子的不变部分通过继承超类得到,鸭子的可变部分,通过组合所需要的行为对象得到。这里用到了以下原则:
设计原则3:多用组合,少用继承
策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此算法让算法的变化,独立于使用算法的客户。
综上,该示例设计如下图:
图5
http://wickedlysmart.com/headfirstdesignpatterns/code.html (souce code download address)
6 C语言的策略模式---鸭子的C实现
二、观察者模式(Observer Pattern)
观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,其他所有依赖者都会收到通知并自动更新。
出版者(Subject)+订阅者(Observer)=观察者模式。
定义观察者模式:类图
Subject是被依赖方,Observer是依赖方;Subject拥有数据并提供访问数据的方法,Observer使用Subject提供的方法访问数据,获取和使用这些数据。Subject负责数据的更新,并通知Observer,Observer接收到数据有更新的通知后,自己决定是否要去获取新数据,并进行相应动作。
松耦合的威力:当两个对象之间的松耦合,他们依然可以交互,但是不太清楚彼此间的细节。观察者提供了一种对象设计,让主题和观察者之间松耦合。
改变主题或观察者之间的任何一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由改变他们。
设计原则4:为了交互对象之间的松耦合设计而努力。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖性降到了最低。