一 入门
摘自<Head First Design Patterns> chapter 1
1 飞翔的鸭子
开发一款模拟鸭子的游戏。首先设计一个鸭子母类,里面有鸭子的叫声、游泳和外形三个成员函数,然后在野鸭和红头鸭两个子类中重写继承的外形函数。
更进一步,要求鸭子会飞,应该如何设计程序呢?
2 两种方法
1) 继承
为 Duck 母类添加 fly() 成员函数,然后各个子类继承 fly()
问题: 并不是所有的鸭子都会飞,比如“煮熟的鸭子”。解决: 不会飞的鸭子自母类继承 fly() 后,重写该函数。
因为鸭子会不会飞,又发现一个bug,并不是所有的鸭子都会叫,比如“大黄鸭”。因此,不同的子类也需要重写 quack()
此时问题又来了,由于开发的是游戏软件,要求游戏每三个月更新一次,每次更新后鸭子的飞行方式和叫声都可能发生新的变化。那么,岂不是每三个月都要重写 RubberDuck 和 DecoyDuck 子类甚至更多子类的 fly() 和 quack() 函数,有点繁琐。
于是,有了第二个方法...
2) 接口 (interface)
视会飞和会叫为一种能力,并将 Flyable 和 Quackable 做成接口 (interface),然后在里面加上相应的函数 fly() 和 quack()
这样,只有会飞的子类实现该接口 Flyable, 并且具有 fly() 函数。同理只有会叫的子类实现接口 Quackable 且有 quack() 函数。
问题又来了,飞行是鸭子的一种行为,不同的鸭子,其各自飞行的方式也不同。显然,每个子类中还得重写 fly() 和 quack()。因此,单纯的使用接口或继承都非最佳。
那么,到底什么方法能完美解决鸭子 fly 和 quack 的问题呢?不着急,先看下面的三个设计原则。
3 三个原则
1) identify what varies and separate them from what stays the same
鸭子类中, 很明显“飞”和“叫”是变化的,于是先把 fly 和 quack 择出来,和其它不变的分隔开来。
2) program to an interface, not an implementation
3) favor composition over inheritance
HAS-A better than IS-A
Java: Duck 母类中定义 FlyBehavior 和 QuackBehavior 的成员对象 (FlyBehavior flyBehavior)
C++: Duck 母类中定义 FlyBehavior 和 QuackBehavior 的指针 (FlyBehavior * pflyBehavior)
4 一个模式
defines a family of algorithms, encapsulates each one, and makes them interchangeable.
(Strategy lets the algorithm vary independently from clients that use it)
1) client
2) encapsulated fly behavior
把飞行封装为接口,里面具体实现不同的飞行方式,即将各种”飞行方式“视为一系列”算法“,添加或者修改算法都在这个接口里面进行。
这样,不同的鸭子子类,只需通过母类中的成员对象 (flyBehavior) 调用相应的”飞行算法“即可。
3) encapsulated quack behavior
Java: 直接使用关键字 interface 便可将 QuackBehavior 定义成接口
C++: 接口 ≈ 抽象基类,将 QuackBehavior 定义为抽象基类,也即声明 quack() 为纯虚函数, 具体代码形式为 “void quack() = 0”
二 进阶
摘自<Design Paterns_Elements of Reusable Object-Oriented Software>
上面偏重于入门,下面开始进阶,偏重于策略模式的适用情景。
1 Intent
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.
2 Applicability
1) many related classes differ only in their behavior
= strategies provide a way to configure a class with one of many behaviors
2) you need different variants of an algorithm
= strategies can be used when these variants are implemented as a class hierarchy of algorithms
3) an algorithm uses data that clients should not know about
= use strategies to avoid exposing complex, algorithm-specific data structures
4) a class defines many behaviors, and these appear as multiple conditional statements in its operations
= move related conditional branches into their own strategy class
3 Sample
many algorithms exist for breaking a stream of text into lines, and
hard-wiring such algorithms into the class that require them is not
desirable.
C++ 实例:
1) class Composition
/* Composition class maintains a collection of Component instances */ class Composition { public: Composition(Compositor*); void Repair(); private: Compositor* _compositor; Component* _components; // the list of components int _componentCount; // the number of components int _lineWidth; // the Composition‘s line width int* _lineBreaks; // the position of linebreaks in components int _lineCount; // the number of lines }; void Composition::Repair () { Coord* natural; Coord* stretchability; Coord* shrinkability; int componentCount; int* breaks; // prepare the arrays with the desired component sizes // ... // determine where the breaks are: int breakCount; breakCount = _compositor->Compose( natural, stretchability, shrinkability, componentCount, _lineWidth, breaks ); // lay out components according to breaks // ... }
2) class Compositor and its subclasses
/* Compositor is an abstract class(also interface) */ class Compositor{ public: virtual int Compose( Coord natural[], Coord stretch[],Coord shrink[], int componentCount, int lineWidth, int breaks[] ) = 0; protected: Compositor(); }; /* Three subclasses */ class SimpleCompositor : public Compositor { public: SimpleCompositor(); virtual int Compose( Coord natural[], Coord stretch[], Coord shrink[], int componentCount, int lineWidth, int breaks[] ); // ... }; class TeXCompositor : public Compositor { public: TeXCompositor(); virtual int Compose( Coord natural[], Coord stretch[], Coord shrink[], int componentCount, int lineWidth, int breaks[] ); // ... }; class ArrayCompositor : public Compositor{ public: ArrayCompositor(int interval); virtual int Compose( Coord natural[], Coord stretch[], Coord shrink[], int componentCount, int lineWidth, int breaks[] ); // ... };
3) instantiation
Composition* quick = new Composition(new SimpleCompositor); Composition* slick = new Composition(new TeXCompositor); Composition* iconic = new Composition(new ArrayCompositor(100));