设计模式作为一种经验的总结,只要使用恰当可以帮助我们解决很多问题,尤其是实现代码的复用和扩展。现在先来看看第一种设计模式,即策略模式(Pattern:Strategy)。
策略模式是指定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
(1)优点:可以动态地改变对象的行为,主要是通过组合和多态的方法来实现;
(2)缺点:使用策略模式会在程序中增加许多策略类或者策略对象,并且要使用策略模式,必须了解所有的strategy,必须了解各个strategy之间的不同点,这样才能选择一个合适的strategy。比如,我们要选择一种合适的旅游出行路线,必须先了解选择飞机、火车、自行车等方案的细节。此时strategy要向客户暴露它的所有实现,这是违反最少知识原则的。
策略模式的设计原则是把一个类中经常改变或者将来可能改变的部分提取出来,作为一个接口(JAVA)或者抽象类(C++),然后在类中包含这个对象的实例,这样类的实例在运行时就可以随意调用实现了这个接口的类的行为,动态改变是用多态实现的。策略模式属于对象行为型模式,主要针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。通常,策略模式适用于当一个应用程序需要实现一种特定的服务或者功能,而且该程序有多种实现方式时使用。
记住三个原则:
(1)封装变化的行为;
(2)多用组合,少用继承;
(3)针对接口编程(C++中用抽象类实现接口的功能),而不是针对实现编程。
策略模式需要以下三个角色:
(1)环境对象:该类中实现了对抽象策略中定义的接口或者抽象类的引用。
(2)抽象策略对象:它可由接口或抽象类来实现。
(3)具体策略对象:它封装了实现同不功能的不同算法。
下面我们来看一个例子,这个例子使用C++来实现,使用C++来实现设计模式比JAVA稍微复杂一点,有很多地方需要大家自己考虑清楚。
先来看头文件:StrategyPattern.h
// 策略模式 #ifndef STRATEGY #define STRATEGY #include <iostream> using std::cout; using std::endl; // 将鸭子的飞行行为抽象成一个算法簇,此为抽象类,扮演接口的功能 class FlyBehavior { public: virtual void fly() = 0; }; // 继承算法的接口,即抽象类,实现鸭子的第一种飞行行为 class FlyWithWings : public FlyBehavior { public: void fly(); }; // 继承算法的接口,即抽象类,实现鸭子的第二种飞行行为 class FlyNoWay : public FlyBehavior { public: void fly(); }; // 将鸭子叫的行为抽象成一个算法簇,此为抽象类,扮演接口的功能 class QuackBehavior { public: virtual void quack() = 0; }; // 继承算法的接口,即抽象类,实现鸭子的呱呱叫 class Quack : public QuackBehavior { public: void quack(); }; // 继承算法的接口,即抽象类,实现鸭子吱吱叫 class Squeak : public QuackBehavior { public: void quack(); }; // 继承算法的接口,即抽象类,实现鸭子不会叫 class MuteQuack : public QuackBehavior { public: void quack(); }; // 鸭子类的基类 class Duck { public: Duck() { cout << "调用Duck构造函数" << endl; flyBehavior = new FlyWithWings(); quackBehavior = new Quack(); } ~Duck() { cout << "调用Duck析构函数" << endl; if (flyBehavior != NULL) { delete flyBehavior; flyBehavior = NULL; } if (quackBehavior != NULL) { delete quackBehavior; quackBehavior = NULL; } } void swim(); void performQuack(); void performFly(); void setFlyBehavior(FlyBehavior *fb); void setQuackBehavior(QuackBehavior *qb); virtual void display() = 0; private: FlyBehavior *flyBehavior; QuackBehavior *quackBehavior; }; // 继承后的鸭子类 class MallardDuck : public Duck { public: void display(); }; class ReadHeadDuck : public Duck { public: void display(); }; class RubberDuck : public Duck { public: void display(); }; class DecoyDuck : public Duck { public: void display(); }; #endif
接下来是头文件对应的实现文件:StrategyPattern.cpp
#include"StrategyPattern.h" // 飞行行为 voidFlyWithWings::fly() { cout << "鸭子飞行1" << endl; } void FlyNoWay::fly() { cout << "鸭子不会飞" << endl; } // 叫行为 void Quack::quack() { cout << "鸭子呱呱叫" << endl; } void Squeak::quack() { cout << "鸭子吱吱叫" << endl; } voidMuteQuack::quack() { cout << "鸭子不会叫" << endl; } // 鸭子基类 void Duck::swim() { cout << "鸭子会游泳" << endl; } voidDuck::performQuack() { if (quackBehavior != NULL) { quackBehavior->quack(); } else { cout << "NULLPoint" << endl; } } void Duck::performFly() { if (quackBehavior != NULL) { flyBehavior->fly(); } else { cout << "NULLPoint" << endl; } } voidDuck::setFlyBehavior(FlyBehavior *fb) { if (flyBehavior != NULL) { free(flyBehavior); flyBehavior = NULL; } flyBehavior = fb; } voidDuck::setQuackBehavior(QuackBehavior *qb) { if (quackBehavior != NULL) { free(quackBehavior); quackBehavior = NULL; } quackBehavior = qb; } // 继承后的鸭子类 voidMallardDuck::display() { cout << "绿头鸭" << endl; } voidReadHeadDuck::display() { cout << "红头鸭" << endl; } voidRubberDuck::display() { cout << "橡皮鸭" << endl; } voidDecoyDuck::display() { cout << "诱饵鸭" << endl; }
最后是策略模式的测试文件:StrategyPatternTest.cpp
#include"StrategyPattern.h" void main() { MallardDuck md; md.display(); md.performFly(); md.performQuack(); md.swim(); cout <<"--------------------------" << endl; md.setFlyBehavior(new FlyNoWay()); md.setQuackBehavior(new Squeak()); md.display(); md.performFly(); md.performQuack(); md.swim(); cout << "结束退出main函数"<< endl; }
该例的运行结果如图1所示。
图1 运行结果
用C++来实现需要注意几点:
(1)一定要注意指针的内存申请和释放的位置,不能随意赋值,否则会出现内存泄露;
(2)构造函数和析构函数要对指针进行特殊处理;
(3)一些基本的错误判断要加上。
下面,我们来看一下这个例子的UML类图,如图2所示。该类图是直接用EA绘图工具将.h文件转换得来的。
图2 策略模式例子的UML类图
下面,我们来分析一下这个例子是怎样实现策略模式的。
如图2所示,我们需要一系列的鸭子,鸭子有不同的种类,有的会飞,有的不会飞,有的会叫,有的不会叫,而且飞行姿势或叫声也是不一样的,如果我们把fly()和quack()两个函数都放在Duck类中,那么每个子类都要去实现这两个方法,这就违背了代码复用的原则。大家可以试想一下,如果有一万种鸭子,当行为改变的时候岂不是要改动一万处的代码?这样就十分麻烦了。所以,通过上述分析可以看出来,鸭子的飞行行为和叫声行为是变化的,而且随着开发需求的改变,这一系列的行为可能要求动态变化,比如说一会儿可以直飞,一会儿可以火箭飞,一会可以呱呱叫,一会儿可以吱吱叫等,这些都会频繁地改变,那么我们根据策略模式的设计原则,把飞行行为和叫声行为从鸭子类中分离出来,构成不同的算法族,即飞行行为算法族和叫声行为算法族,然后在Duck中定义这些算法族的对象(C++中用指针实现),在Duck中这些都是基类,而在实际运行时可以用set相应函数改变这些行为,用多态的方法来实现动态变化对象行为的目的。当我们需要增加新的飞行行为或者叫声行为时,只需要再实现一些飞行行为或者叫声行为即可,并不需要改变Duck类及其子类的任何东西,所以可以看出这一实现方式是比较良好的。
总之,我们整体来看就是:
(1)鸭子的飞行行为被抽象出一个FlyBehavior接口(C++中是抽象类),不同的飞行行为都去实现这个接口;
(2)鸭子的叫声行为被抽象出一个QuackBehavior接口(C++中是抽象类),不同的叫声行为都去实现这个接口;
(3)鸭子的基类Duck中,包含了这两个抽象类的指针和其他的一些方法,当鸭子需要飞行或者叫的时候,只需要调用其中的performFly()或者performQuack()函数去执行抽象类的具体实现类中的方法即可,这个过程是通过指针和多态来实现的;
(4)鸭子的子类可以通过setFlyBehavior()和setQuackBehavior()函数来动态设置鸭子的飞行行为和叫声行为,从而实现这些对象行为的动态变化;
(5)当需要增加飞行行为或者叫声行为时,只需要再实现抽象类即可,不需要改变鸭子类及其子类的任何成员。
策略模式有很大的用处,大家可以根据自己的项目需求来决定是否使用这种模式,当然,还会有很多设计模式供大家选择,后面会陆续说到,希望可以和大家共同学习共同进步。