策略模式(Strategy Pattern)
策略模式(Strategy Pattern)
抛开晦涩的定义,首先看一个例子:
我们想要创建一个模拟鸭子的游戏,在这个游戏中,会有各种类型的鸭子,比如mallard duck,red head duck,rubber duck(除了rubber duck(橡皮鸭),看见这其余两种鸭子很好奇,于是查找相关图片,发现mallard duck是绿头鸭,red head duck是红头鸭,自己生活中还没有见过,有趣,哈哈!三种鸭子图片如下所示)。
回归话题,在这个模拟鸭子的游戏中,各种鸭子均有两种能力,即游泳(swim)和呱呱叫(quack)。根据以上描述,进行类设计。
假设以前我们没有学过策略模式,那么我们一定会设计一个基类duck,具有swim和quack属性,然后令这三种类型的鸭子继承基类duck。具体类图如下:
根据这个类设计图可以编写如下代码:
#include <iostream> using namespace std; class Duck { public: void quack(){cout << " I\'m quacking!" << endl;} void swim() {cout << " I\'m swimming!" << endl;} virtual void display()=0; }; class MallardDuck:public Duck { public: void display() { cout << "This is Mallard Duck:" << endl; quack(); swim(); } }; class RedHeadDuck :public Duck { public: void display() { cout << "This is Red Head Duck:" << endl; quack(); swim(); } }; class RubberDuck :public Duck { public: void display() { cout << "This is Rubber Duck:" << endl; quack(); swim(); } }; int main() { MallardDuck mallard_duck; RedHeadDuck redhead_duck; RubberDuck rubber_duck; mallard_duck.display(); redhead_duck.display(); rubber_duck.display(); system("pause"); return 0; }
View Code
程序结果:
大多数人会想到上述的设计方式,这种设计方式在以后的扩展中会遇到问题,如果要在这个模拟鸭子的游戏中新添加一个飞翔的功能,那我们可能就会轻易的在基类duck中添加一个fly函数,然后不同种类的鸭子继承此基类,实行飞行,改变后的类图如下:
代码:
#include <iostream> using namespace std; class Duck { public: void quack(){cout << " I\'m quacking!" << endl;} void swim() {cout << " I\'m swimming!" << endl;} void fly() { cout << " I\'m flying!" << endl; } virtual void display()=0; }; class MallardDuck:public Duck { public: void display() { cout << "This is Mallard Duck:" << endl; quack(); swim(); fly(); } }; class RedHeadDuck :public Duck { public: void display() { cout << "This is Red Head Duck:" << endl; quack(); swim(); fly(); } }; class RubberDuck :public Duck { public: void display() { cout << "This is Rubber Duck:" << endl; quack(); swim(); fly(); } }; int main() { MallardDuck mallard_duck; RedHeadDuck redhead_duck; RubberDuck rubber_duck; mallard_duck.display(); redhead_duck.display(); rubber_duck.display(); system("pause"); return 0; }
View Code
程序结果:
这时发现一个问题,Rubber Duck(橡皮鸭)居然也会飞,我们并不想让橡皮鸭飞行,怎么做呢?我们可以在橡皮鸭的类里面将fly覆盖掉,并且不做任何事情。覆盖后的类设计图为:
代码:
#include <iostream> using namespace std; class Duck { public: void quack(){cout << " I\'m quacking!" << endl;} void swim() {cout << " I\'m swimming!" << endl;} void fly() { cout << " I\'m flying!" << endl; } virtual void display()=0; }; class MallardDuck:public Duck { public: void display() { cout << "This is Mallard Duck:" << endl; quack(); swim(); fly(); } }; class RedHeadDuck :public Duck { public: void display() { cout << "This is Red Head Duck:" << endl; quack(); swim(); fly(); } }; class RubberDuck :public Duck { void fly(){} public: void display() { cout << "This is Rubber Duck:" << endl; quack(); swim(); fly(); } }; int main() { MallardDuck mallard_duck; RedHeadDuck redhead_duck; RubberDuck rubber_duck; mallard_duck.display(); redhead_duck.display(); rubber_duck.display(); system("pause"); return 0; }
View Code
程序结果:
这样可以解决使得橡皮鸭不能飞行,但是也会出现问题,当我们每次出现一个新类型的鸭子时,就必须要检查是否能够飞行进而决定是否覆盖飞行函数。有些鸭子甚至不能叫(quack),这样一来叫(quack)行为也必须要检查。要记住,我们在这个例子中只是举了三种行为quack,swim,fly。在真正的工程中可能会具有很多行为,例如几十种,新的鸭子出现时,我们便需要一一的去与这几十种进行检查,这样并不便于维护。因此这种设计方法仍有待于改进。
针对上面提到的问题,经过苦苦思索,我们可以想到一种新的解决方案,即将quack,swim,fly三种行为分离出来,独自形成单独的抽象类,然后当有新型鸭子出现时,只需要关注这个鸭子本身具有什么样的行为属性,并将该新鸭子类继承相关的行为抽象类,然后实现其方法,具体类的设计如下图所示:
程序:
#include <iostream> using namespace std; class Duck { public: void swim() {cout << " I\'m swimming!" << endl;} virtual void display()=0; }; class Quackable { public: virtual void quack() = 0; }; class Flyable { public: virtual void fly() = 0; }; class MallardDuck:public Duck,public Quackable,public Flyable { void quack() { cout << " I\'m quacking" << endl; } void fly() { cout << " I\'m flying" << endl; } public: void display() { cout << "This is Mallard Duck:" << endl; quack(); swim(); fly(); } }; class RedHeadDuck :public Duck , public Quackable, public Flyable { void quack() { cout << " I\'m quacking" << endl; } void fly() { cout << " I\'m flying" << endl; } public: void display() { cout << "This is Red Head Duck:" << endl; quack(); swim(); fly(); } }; class RubberDuck :public Duck, public Quackable { void quack() { cout << " I\'m quacking" << endl; } public: void display() { cout << "This is Rubber Duck:" << endl; quack(); swim(); } }; int main() { MallardDuck mallard_duck; RedHeadDuck redhead_duck; RubberDuck rubber_duck; mallard_duck.display(); redhead_duck.display(); rubber_duck.display(); system("pause"); return 0; }
View Code
运行结果:
通过这种方式,将可能改变的函数行为与不变的函数行为分离(即将quack和fly分离),这样当具有一种新类型的鸭子出现时只需要考虑鸭子本身的具有哪些行为特征,不用像原来方法那样需要考虑基类的所有行为并且决定是否去覆盖或改变。但是,这样也会出现问题,代码无法实现复用。例如当多个种类的鸭子具有同一种飞行(fly)时,我们需要在这些种类的鸭子中添加相同的飞行函数,无法达到复用,并且如果以后这个游戏需要更改鸭子的飞行行为方式,那么此种设计方法将会使我们进入到鸭子的每一个类中进行相同的修改,过于麻烦。
上面讲述许多常见的有问题的类设计思维方式,最后会导致代码维护扩展难度增加,那么最后给出一个终极版本解决这个类的设计问题!在解决之前,根据这个类介绍一下设计原则。
设计原则1:找出应用中可能需要变化之处,将变化之处与不需要变化的代码独立出来,不要混合在一起!
这句话看似简单,但容易忽略,以这个鸭子游戏的类设计为例,鸭子有三个行为特征(swim,quack,fly)。基本所有的鸭子都可以swim,所以此行为不需要变化。叫声(quack)根据每个鸭子的种类不同,叫声会不同,故可以变化。飞翔(fly)同理,有的鸭子可飞,有的鸭子不能飞。
综上,不需要变化的是swim,需要变化的是fly,quack。因此我们为这两种行为建立一个新类。其实在之前的类设计中,最后一种方式已经实现将变化与不变化进行分离,但是这还不够,我们同时需要使用下面一个原则!
设计原则2:针对接口编程(JAVA接口,C++抽象类),而不是针对实现编程。
利用此原则可以实现多态,在运行时动态地改变行为。在满足设计原则1时我们为独立的fly和quack分别建立两个抽象类(Flyable和Quackable)。以前的做法是在鸭子中对这两个抽象类进行具体实现,那么这次进行改变,使得鸭子不需要对这两个抽象类进行实现,而是由另外单独的类进行实现。这两个类设计如下图:
设计原则3:多用组合,少用继承。
使用组合建立系统具有很大的弹性,例如鸭子的设计,将quack类与fly类组合到鸭子的抽象类设计中,利用多态的特性进行编程会取得很好效果。类设计如下图所示:
在上面的类设计中,我们可以观察到是将Flyable和Quackable抽象类放到Duck中,这样可以更好的利用多态性。注意在C++中使用抽象类时,抽象类不能创建实例,因此需要将flyable和quackable设置成为指针形式。具体程序代码如下:
#include <iostream> using namespace std; class Quackable { public: virtual void quack() = 0; }; class Quack :public Quackable { void quack(){ cout << " I\'m quacking(呱呱叫)" << endl; } }; class Squeak :public Quackable { void quack() { cout << " I\'m quacking(吱吱叫)" << endl; } }; class MuteQuack :public Quackable { void quack() { cout << " I can\'t quack!" << endl; } }; class Flyable { public: virtual void fly() = 0; }; class FlyWithWings:public Flyable{ void fly() { cout << " I\'m flying!" << endl; } }; class FlyWithNoWing :public Flyable { void fly() { cout << " I can\'t fly!" << endl; } }; class Duck { public: Flyable *flyable; Quackable *quackable; void swim() { cout << " I\'m swimming!" << endl; } void quack() { quackable->quack(); } void fly() { flyable->fly(); } virtual void display() = 0; }; class MallardDuck:public Duck{ public: MallardDuck() { flyable = new FlyWithWings(); quackable = new Quack(); } void display() { cout << "This is Mallard Duck:" << endl; } }; class RedHeadDuck :public Duck { public: RedHeadDuck() { flyable = new FlyWithWings(); quackable = new Quack(); } void display() { cout << "This is Red Head Duck:" << endl; } }; class RubberDuck :public Duck { public: RubberDuck() { flyable = new FlyWithNoWing(); quackable = new Quack(); } void display() { cout << "This is Rubber Duck:" << endl; } }; int main() { Duck *dk = new MallardDuck(); dk->display(); dk->quack(); dk->swim(); dk->fly(); dk = new RedHeadDuck(); dk->display(); dk->quack(); dk->swim(); dk->fly(); dk = new RubberDuck(); dk->display(); dk->quack(); dk->swim(); dk->fly(); system("pause"); return 0; }
View Code
在上面的main函数中,也可以直接将所有输出信息整合到同一个函数中进行打印,在此不进行过多编程。结果如下所示:
我们可以发现在每个类型的鸭子构造函数中对行为进行设定,其实这样做还是不够弹性,如果运行程序中我们需要改变鸭子的叫声方式,那么上述程序将不能实现。我们需要在鸭子的抽象类中增加一个可以改变鸭子叫声的函数,这样就可以动态的进行改变,飞行等其他功能类似如此。代码如下:
#include <iostream> using namespace std; class Quackable { public: virtual void quack() = 0; }; class Quack :public Quackable { void quack(){ cout << " I\'m quacking(呱呱叫)" << endl; } }; class Squeak :public Quackable { void quack() { cout << " I\'m quacking(吱吱叫)" << endl; } }; class MuteQuack :public Quackable { void quack() { cout << " I can\'t quack!" << endl; } }; class Flyable { public: virtual void fly() = 0; }; class FlyWithWings:public Flyable{ void fly() { cout << " I\'m flying!" << endl; } }; class FlyWithNoWing :public Flyable { void fly() { cout << " I can\'t fly!" << endl; } }; class Duck { public: Flyable *flyable; Quackable *quackable; void setFlyable(Flyable *f) { flyable = f; } void setQuackable(Quackable *q) { quackable = q; } void swim() { cout << " I\'m swimming!" << endl; } void quack() { quackable->quack(); } void fly() { flyable->fly(); } virtual void display() = 0; }; class MallardDuck:public Duck{ public: MallardDuck() { flyable = new FlyWithWings(); quackable = new Quack(); } void display() { cout << "This is Mallard Duck:" << endl; } }; class RedHeadDuck :public Duck { public: RedHeadDuck() { flyable = new FlyWithWings(); quackable = new Quack(); } void display() { cout << "This is Red Head Duck:" << endl; } }; class RubberDuck :public Duck { public: RubberDuck() { flyable = new FlyWithNoWing(); quackable = new Quack(); } void display() { cout << "This is Rubber Duck:" << endl; } }; int main() { Duck *dk = new MallardDuck(); dk->display(); dk->quack(); dk->swim(); dk->fly(); cout << "~~~~~~~~~~~~Change Fly and Quack!~~~~~~~~~~~~" << endl; dk->setFlyable(new FlyWithNoWing()); dk->setQuackable(new Squeak()); dk->display(); dk->quack(); dk->swim(); dk->fly(); system("pause"); return 0; }
View Code
程序结果:
至此,整个类的设计结束。
上述所用的设计模式称为策略模式,下面看一下策略模式的官方定义:
策略模式:该模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
算法族指的上面鸭子的各种行为,并且将他们都封装起来,然后将其组合到鸭子的类中,鸭子属于使用算法的客户。这个模式很好的实现使用者与被使用者的分离。
END!
参考文献:
[1] Head First 设计模式