《HeadFirst设计模式》一书非常有趣,图文并茂,诙谐生动,学习时难免喜形于色。相比之下GOF的作品我更愿意作为工具书,最终权威的解读。
该书的描述为Java语言,但编程的思想是通用的。
开篇讲策略模式。一时心痒难耐,决定举一反三,推出一个C++的示例。
模式是一个套路,里面的基本思想才是精华。毕竟咱们程序员都是讲实战的,理论结合实际才是硬道理。
策略模式用到了三个朴素的基本编程原则,这个不是由谁谁说了就算的,而是由无数先辈们们经过无数次的惨痛教训后总结出来的。当然遵不遵循,则是个人意愿了。
三个原则如下。
① 经常变动的代码应该提出来。
② 多用组合,少用继承。
③ 面向接口编程。
接下来,我会通过示例来讲述一下这三个原则如何在实战中应用。
在编程的世界里,唯一不变的东西就是需求永远是变的。作为程序员,这点觉悟应该还是要有的。示例中需求也是层层递进。
1、 基本需求及实现
现在你是老板,要雇人干活。干活就要管人家吃饭,统一标准订餐“汉堡包+鸡翅+可乐”。当然为防止有不相关的人来蹭饭,大家吃饭前都先介绍一下自己。
类图是这样的。
代码见FirstStep.cpp。(代码在下面的附录中)
运行结果为:
我是干活的人类 吃汉堡包+鸡翅+可乐
OK,一切正常,工人们也没人抱怨。
2、 需求变更及实现
由于人工智能的飞速进步,你决定雇佣机器人来为你工作。
现在类图变成这样。
代码见SecondStep_1.cpp。(代码在下面的附录中)
运行结果为:
我是干活的人类 吃汉堡包+鸡翅+可乐 我是干活的机器人 吃汉堡包+鸡翅+可乐
这样的结果,冷汗了吧。看过《人工智能》的都应该记得那个情节,吃了人类食物的机器人要赶紧去修理,晚了可都全完了。
这个时候,你幡然悔悟,机器人不吃饭,而是应该是吃电(充电)。
怎么改呢?覆盖Robot的Eat方法。听起来不错,不过要是再雇佣个吃电的雇员(比如钢铁侠),吃电这个方法不是要实现两次(代码不能复用,改起来也要改两个地方)。
我们的基础三原则应该能提供些帮助。经常变动的代码,Eat方法明显是,提出来。针对接口编程,我们把Eat方法提取为一个接口,调用者不再关心他的具体实现。
类图如下。代码见SecondStep_2.cpp。(代码在下面的附录中)
运行结果为:
我是干活的人类 吃汉堡包+鸡翅+可乐 我是干活的机器人 进行充电
终于正常了^_^。
3、 更多需求变更
你的业务越做越大,来帮你干活的人也多了。大力水手来了,汽车人来了,树人也来了。这些人你也要管饭,大力水手吃菠菜,汽车人吃汽油,树人嘛光合作用就行了。而且,天天吃快餐谁也受不了,工人们要求可以随时换餐。步骤2里设计的框架很灵活,我们在原来基础上改动不大就可以实现。
Worker类里增加SetEatPtr,可以随时换餐。
类图就变成如下的样子。
代码参见ThirdStep.cpp。(代码在下面的附录中)
运行结果:
我是干活的人类 吃汉堡包+鸡翅+可乐 我是干活的机器人 进行充电 我是干活的汽车人 加汽油 我是干活的树人 进行光合作用 我是干活的人类 吃菠菜 我是干活的人类 吃大米
好了,现在大家不用一直吃快餐了,有人吃菠菜,有人吃大米,汽车人树人也有自己的进食方式。这套模式便于扩展,比如火星人来给你干活了,直接再实现个火星人吃饭的算法即可。
策略模式归总
现在大家明白了吧,策略模式,就是把经常改变的算法提取出来,形成一个算法族。算法族下的算法可以相互替换。
但是最重要的东西,我认为还是开头提到的三原则,根据这些原则就可以研究出自己的模式。^_^
代码附录
FirstStep.cpp
#pragma once // StrategyPattern.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> using namespace std; class Worker { public: Worker() : m_strIntroduce("我是干活的") {} virtual ~Worker() {} //自我介绍 virtual void Introduce() { cout<<m_strIntroduce.c_str()<<endl; } //吃饭 virtual void Eat() { cout<<"吃汉堡包+鸡翅+可乐"<<endl; } protected: string m_strIntroduce; }; class Human : public Worker { public: Human() : Worker() { m_strIntroduce += "人类"; } }; int _tmain(int argc, _TCHAR* argv[]) { Worker& worker_a = Human(); worker_a.Introduce(); worker_a.Eat(); cout<<endl; return 0; }
// StrategyPattern.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> using namespace std; class Worker { public: Worker() : m_strIntroduce("我是干活的") {} virtual ~Worker() {} //自我介绍 virtual void Introduce() { cout<<m_strIntroduce.c_str()<<endl; } //吃饭 virtual void Eat() { cout<<"吃汉堡包+鸡翅+可乐"<<endl; } protected: string m_strIntroduce; }; class Human : public Worker { public: Human() : Worker() { m_strIntroduce += "人类"; } }; class Robot : public Worker { public: Robot() : Worker() { m_strIntroduce += "机器人"; } }; int _tmain(int argc, _TCHAR* argv[]) { Worker& worker_a = Human(); worker_a.Introduce(); worker_a.Eat(); cout<<endl; Worker& worker_b = Robot(); worker_b.Introduce(); worker_b.Eat(); cout<<endl; return 0; }
// StrategyPattern.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> using namespace std; class EatInterface { public: virtual ~EatInterface() {} //提供吃的接口 virtual void Eat() = 0; }; class EatFastFood : public EatInterface { public: virtual void Eat() { cout<<"吃汉堡包+鸡翅+可乐"<<endl; } }; class EatBattery : public EatInterface { public: virtual void Eat() { cout<<"进行充电"<<endl; } }; class Worker { public: Worker() : m_strIntroduce("我是干活的") , pEatPtr(NULL) {} virtual ~Worker() { if (pEatPtr != NULL) { delete pEatPtr; pEatPtr = NULL; } } //自我介绍 virtual void Introduce() { cout<<m_strIntroduce.c_str()<<endl; } //吃饭 virtual void SimulateEat() { pEatPtr->Eat(); } protected: string m_strIntroduce; EatInterface* pEatPtr; }; class Human : public Worker { public: Human() : Worker() { m_strIntroduce += "人类"; pEatPtr = new EatFastFood; } }; class Robot : public Worker { public: Robot() : Worker() { m_strIntroduce += "机器人"; pEatPtr = new EatBattery; } }; int _tmain(int argc, _TCHAR* argv[]) { Worker& worker_a = Human(); worker_a.Introduce(); worker_a.SimulateEat(); cout<<endl; Worker& worker_b = Robot(); worker_b.Introduce(); worker_b.SimulateEat(); cout<<endl; return 0; }
// StrategyPattern.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> using namespace std; class EatInterface { public: virtual ~EatInterface() {} //提供吃的接口 virtual void Eat() = 0; }; class EatFastFood : public EatInterface { public: virtual void Eat() { cout<<"吃汉堡包+鸡翅+可乐"<<endl; } }; class EatRice : public EatInterface { public: virtual void Eat() { cout<<"吃大米"<<endl; } }; class EatSpinach : public EatInterface { public: virtual void Eat() { cout<<"吃菠菜"<<endl; } }; class EatBattery : public EatInterface { public: virtual void Eat() { cout<<"进行充电"<<endl; } }; class EatOil : public EatInterface { public: virtual void Eat() { cout<<"加汽油"<<endl; } }; class EatLight : public EatInterface { public: virtual void Eat() { cout<<"进行光合作用"<<endl; } }; class Worker { public: Worker() : m_strIntroduce("我是干活的") , m_pEatPtr(NULL) {} virtual ~Worker() { ClearEatPtr(); } void SetEatPtr(EatInterface* pEatPtr) { ClearEatPtr(); m_pEatPtr = pEatPtr; } //自我介绍 virtual void Introduce() { cout<<m_strIntroduce.c_str()<<endl; } //吃饭 virtual void SimulateEat() { if (m_pEatPtr != NULL) { m_pEatPtr->Eat(); } } private: void ClearEatPtr() { if (m_pEatPtr != NULL) { delete m_pEatPtr; m_pEatPtr = NULL; } } protected: string m_strIntroduce; EatInterface* m_pEatPtr; }; class Human : public Worker { public: Human() : Worker() { m_strIntroduce += "人类"; SetEatPtr(new EatFastFood); } }; class Robot : public Worker { public: Robot() : Worker() { m_strIntroduce += "机器人"; SetEatPtr(new EatBattery); } }; class CarMan : public Worker { public: CarMan() : Worker() { m_strIntroduce += "汽车人"; SetEatPtr(new EatOil); } }; class TreeMan : public Worker { public: TreeMan() : Worker() { m_strIntroduce += "树人"; SetEatPtr(new EatLight); } }; int _tmain(int argc, _TCHAR* argv[]) { Worker& worker_a = Human(); worker_a.Introduce(); worker_a.SimulateEat(); cout<<endl; Worker& worker_b = Robot(); worker_b.Introduce(); worker_b.SimulateEat(); cout<<endl; Worker& worker_c = CarMan(); worker_c.Introduce(); worker_c.SimulateEat(); cout<<endl; Worker& worker_d = TreeMan(); worker_d.Introduce(); worker_d.SimulateEat(); cout<<endl; Worker& worker_e = Human(); worker_e.SetEatPtr(new EatSpinach); worker_e.Introduce(); worker_e.SimulateEat(); cout<<endl; Worker& worker_f = Human(); worker_f.SetEatPtr(new EatRice); worker_f.Introduce(); worker_f.SimulateEat(); cout<<endl; return 0; }