第23章烤羊肉串引来的思考--命令模式
23.1 吃烤羊肉串
23.2 烧烤摊VS烧烤店
要羊肉做生意的马路游击队,因为要吃烤肉的人太多,大家都希望能最快吃到肉串,而烤肉老板只有一个人,所以有些混乱。买羊肉串的人都盯着烤肉上去了。他们只关心哪一串多,哪一串少,哪一串考得好,这个其实就是行为请求着与行为实现者之间的紧耦合。
对请求排队或记录请求日志,以及支持可撤销的操作等行为时,行为请求者与行为实现者的紧耦合是不太合适的。而在门店我们只需要给接待我们的服务员说我们要什么就可以了。收钱的时候也不会多收和少收。
23.3 紧耦合设计
路边烤肉的对应。
23.4 松耦合设计
#pragma once #include "Barbecuer.h" class Command { public: Command(BarBecuer* receive) { m_pBarbecuer = receive; }; virtual void ExcuteCommand(void) = 0; protected: BarBecuer* m_pBarbecuer;//野餐烤肉的人 }; //烤羊肉串 class BakeMuttonCommand : public Command { public: BakeMuttonCommand(BarBecuer* receive) :Command(receive) { }; void ExcuteCommand(void) { m_pBarbecuer->BakeMutton(); }; }; //烤鸡翅 class BakeChickenCommand : public Command { public: BakeChickenCommand(BarBecuer* receive) :Command(receive) { }; void ExcuteCommand(void) { m_pBarbecuer->BakeChickenWing(); }; };
服务员类
#pragma once #include "command.h" class Waiter { public: //设置订单 void SetOrder(Command* pCommand) { m_pCommand = pCommand; }; //通知执行 void Notify() { m_pCommand->ExcuteCommand(); }; private: Command* m_pCommand; };
羊肉串师傅
#pragma once #include <iostream> class BarBecuer { public: void BakeMutton() { std::cout << "烤羊肉串" << std::endl; }; void BakeChickenWing() { std::cout << "烤鸡翅" << std::endl; }; };
客户端
#include "Waiter.h" int _tmain(int argc, _TCHAR* argv[]) { BarBecuer* boy = new BarBecuer(); Command* bakeMuttonCommand1 = new BakeMuttonCommand(boy); Command* bakeMuttonCommand2 = new BakeMuttonCommand(boy); Command* bakeChickenWingCommand1 = new BakeChickenCommand(boy); Waiter* girl = new Waiter(); girl->SetOrder(bakeMuttonCommand1); girl->Notify(); girl->SetOrder(bakeMuttonCommand2); girl->Notify(); girl->SetOrder(bakeChickenWingCommand1); girl->Notify(); delete bakeMuttonCommand1; delete bakeMuttonCommand2; delete bakeChickenWingCommand1; delete boy; delete girl; return 0; }
上面这个实现有下面这些问题点:1.用户并不是只点一个菜,2.如果没有鸡翅了,不应该由用户来判断,3.点的菜需要有日志,4.如果菜点了太多,没有取消的操作。
23.5 松耦合后
这是小菜写的第三版的代码。
#pragma once #include "command.h" #include <iostream> #include <list> class Waiter { public: //设置订单 void SetOrder(Command* pCommand) { m_list.push_back(pCommand); }; //通知执行 void Notify() { //m_pCommand->ExcuteCommand(); if( !m_list.empty() ) { m_iter = m_list.begin(); while(m_iter != m_list.end()) { (*m_iter)->ExcuteCommand(); ++m_iter; } } }; //取消订单 void CancelOrder(Command* pCommand) { if( !m_list.empty() ) { m_list.remove(pCommand); } }; private: std::list<Command*> m_list; std::list<Command*>::iterator m_iter; //Command* m_pCommand; };
#include "Waiter.h" int _tmain(int argc, _TCHAR* argv[]) { //开店前的准备 BarBecuer* boy = new BarBecuer(); Command* bakeMuttonCommand1 = new BakeMuttonCommand(boy); Command* bakeMuttonCommand2 = new BakeMuttonCommand(boy); Command* BakeChickenCommand1 = new BakeChickenCommand(boy); Waiter* girl = new Waiter(); //开门营业顾客点菜 girl->SetOrder(bakeMuttonCommand1); girl->SetOrder(bakeMuttonCommand2); girl->SetOrder(BakeChickenCommand1); girl->Notify(); delete boy; delete bakeMuttonCommand1; delete bakeMuttonCommand2; delete BakeChickenCommand1; delete girl; return 0; }
23.6 命令模式
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持撤销的操作。
Command类,用来声明执行操作的接口。
ConcreteCommand类,将一个接受者对象绑定于一个动作,调用接受者相应的操作,以实现Execute。
#pragma once #include "Receiver.h" class Command { public: Command(Receiver* receiver) { m_receiver = receiver; }; virtual void Execute(void) = 0; protected: Receiver* m_receiver; }; class ConcreteCommand : public Command { public: ConcreteCommand(Receiver* receiver) :Command(receiver) { }; void Execute(void) { m_receiver->Action(); }; };
Invoker类,要求该命令执行这个请求。
#pragma once #include "Command.h" class Invoker { public: void SetCommand(Command* pCommand) { m_pCommand = pCommand; }; void ExecuteCommand() { m_pCommand->Execute(); }; private: Command* m_pCommand; };
Receiver类,直到如何实施与执行一个与请求相关的操作,任何类都可能作为一个接受者。
#pragma once #include <iostream> class Receiver { public: void Action() { std::cout << "执行请求" << std::endl; }; };
客户端
#include "stdafx.h" #include "Invoker.h" int _tmain(int argc, _TCHAR* argv[]) { Receiver* rec = new Receiver(); Invoker* invoker = new Invoker(); Command* command = new ConcreteCommand(rec); invoker->SetCommand(command); invoker->ExecuteCommand(); delete rec; delete invoker; delete command; return 0; }
23.7 命令模式作用
第一,它能较容易地设计一个命令队列;第二,在需要的情况下,可以较容易地将命令计入日志;第三,允许接收请求的一方决定是否要否决请求;第四,可以容易地实现对请求的撤销和重做;第五,由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。其实还有最关键的优点就是命令模式把请求一个操作的对象与直到怎么执行一个操作的对象分割开。
敏捷开发原则告诉我们,不要为代码添加基于猜测的,实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
再回到羊肉串的话题来,当服务员搞错价格的时候,服务员做了记录,也就是记日志,所以可以很快纠正过来。而单就烤肉串的人,哪里记得住烤了多少串,如果人一多,则必定会搞错。