设计模式学习(十三) 命令模式 Command

1.命令模式的定义及类图

1.1 命令模式的定义

传统的类调用别的类的过程分为三步:创建目标对象的实例;设置调用所需类的函数;调用目标对象的方法。而Command模式即为使用一个专门的类对这种调用过程加以封装,请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
其主要适用于将一个请求封装为一个对象(类),从而可以使用不同的请求对客户端进行参数化;可以对这些请求进行排队或记录请求日志的同时,也可以支持增删等操作。

1.2 命令模式的类图

其类图如下:
命令模式类图
关于命令模式的类图,网上的版本各有千秋,主要体现在Invoker、Receive和Concrete三个类与Client的关系上。综合比较后,我认为Client客户端与Invoker调用者,为实打实的关联关系,而非传统的依赖关系;而Client客户端与Receive执行者应为依赖关系;同时,Client客户端与具体命令类ConcreteCommand仍保持依赖关系。
我个人认为的原因如下:

  1. Invoker调用者,对于客户端来说,其作为命令的收集者以及命令执行的发起者,其作为命令模式实现功能的关键,必须保证其在Client的函数中被实例化,并使用其起到作用,这是一种长期性的,非偶然性使用的关系,所以两者之间的关系应为关联关系。具体来说,是Client客户端对Invoker调用者的单向关联关系;
  2. Receive执行者,对于客户端来说,可能有的执行者千千万,比如你可以让小刘去做个Word,也可以让小李去做个Word,客户端调用哪个执行者,是存在一定的偶然性的。同时,对应的执行者在完成任务后,也会暂时失去与客户端的联系,可见两者关系是一种临时性的,可见两者关系应为依赖关系(当然,还有一种需要和ConcreteCommand一起用到的情况,在下面具体描述)。具体来说,是Client客户端对Receive执行者的单向依赖关系。
  3. ConcreteCommand具体命令,对于客户端来说,所调用的Invoker调用者中存在多少个命令,这些命令有多少种类,其实其并不关心,其重点是调用Invoker调用者来将所存储的命令分发出去执行,至于这些任务是什么,与客户端其实是没有关系的。但是,在对于Invoker添加了增删命令等操作之后,可能需要Receive执行者和ConcreteCommand具体命令来实现增删过程,此时两者都是作为局部变量的状态出现在客户端中,在执行完增删等任务之后就与客户端毫无联系,所以Client类和ConcreteCommand类也是一种临时的,偶然的关系,故两者为依赖关系。具体来说,是Client客户端对ConcreteCommand具体命令的单向依赖关系。

那么在理清楚这些关系后,再来审视这个类图:

  1. Invoker类,为命令的收集者与发布者,这代表其中必然有存储命令的数据结构,也有相应的执行命令的相关函数。同时其与Command接口类为聚合关系,可知Command接口类的实例化数据会被存储到其中的数据结构中(无论是单个对象,或者list类的容器结构),可以对这些命令进行增删与记录等操作;
  2. Command接口类:为命令的接口类,负责定义项目中所需例子的接口函数,为所有的Command类提供预定义的过程;
  3. ConreteCommand类:为命令的实例化类,其通过继承Command接口,重载其中的虚函数实现所需的函数功能(无论是定义所需要执行的对象Receive类,还是记录每次需执行命令的日志等);
  4. Receive类:为命令的实际执行者,其在实例化后,会被各个ConreteCommand类所调用,从而实现命令到执行的绑定过程。而其可能有多种种类,并且可能一个命令不止一个种类去执行;
  5. Client类:为客户端,其在调用时只需要new出Invoker类作为调用者,对存储在其中的命令进行发布即可。对于Invoker类的增删命令等操作,可以在客户端中进行,也可以在底层函数进行(当在客户端进行时,其与Receive类和ConreteCommand类成依赖关系)

2.命令模式的代码实现

本例中的Invoker类为Boss类,表示负责收集命令与发布的老板;而Command类为Make_Office类,表示一个制作Office文件的接口,所以其对应的具体ConreteCommand实例类即为Make_Word类、Make_Excel类以及Make_Powerpoint类,而负责执行这些命令的为Staff类,代表执行老板命令的公司职员。
最后在主函数中new出需要使用的Staff类的公司职员,ConreteCommand类的老板命令以及Invoker类的大老板。值得一提的是,主函数中的东西不能全部代表客户端类Client,因为有些操作其实是可以在别的地方完成的。比如对于Boss类的赋值过程,都可以在别的地方实现,主函数只需要使用Boss类的SubmitCommand函数发布命令即可。

#include <iostream>
#include <string>
#include <list>
using namespace std;

class Staff
{
public:
	Staff(string name) : m_name(name) {}
	void Make_Word()
	{
		cout << "Boss command staff " << m_name << " to make a word!" << endl;
	}
	void Make_Excel()
	{
		cout << "Boss command staff " << m_name << " to make a excel!" << endl;
	}
	void Make_Powerpoint()
	{
		cout << "Boss command staff " << m_name << " to make a ppt!" << endl;
	}
private:
	string m_name;
};

class Command
{
public:
	virtual void Make_Office() = 0;
};

class Word_Command : public Command
{
public:
	Word_Command(Staff* staff) : m_staff(staff) {}
	void Make_Office()
	{
		m_staff->Make_Word();
	}
private:
	Staff* m_staff;
};

class Excel_Command : public Command
{
public:
	Excel_Command(Staff* staff) : m_staff(staff) {}
	void Make_Office()
	{
		m_staff->Make_Excel();
	}
private:
	Staff* m_staff;
};

class Powerpoint_Command : public Command
{
public:
	Powerpoint_Command(Staff* staff) : m_staff(staff) {}
	void Make_Office()
	{
		m_staff->Make_Powerpoint();
	}
private:
	Staff* m_staff;
};

class Boss
{
public:
	Boss()
	{
		m_commandlist.clear();
	}
	void AddCommand(Command* command)
	{
		m_commandlist.push_back(command);
	}
	void RemoveCommand(Command* command)
	{
		m_commandlist.remove(command);
	}
	void SubmitCommand()
	{
		for (auto &s : m_commandlist)
		{
			s->Make_Office();
		}
		m_commandlist.clear();
	}
private:
	list<Command*> m_commandlist;
};

int main()
{
	Staff* mike = new Staff("mike");
	Staff* lily = new Staff("lily");
	Word_Command* word1 = new Word_Command(mike);
	Excel_Command* excel1 = new Excel_Command(mike);
	Powerpoint_Command* ppt1 = new Powerpoint_Command(lily);

	Boss* jack = new Boss;
	jack->AddCommand(word1);
	jack->AddCommand(excel1);
	jack->AddCommand(ppt1);
	jack->SubmitCommand();

	jack->AddCommand(word1);
	jack->AddCommand(excel1);
	jack->AddCommand(ppt1);
	jack->RemoveCommand(excel1);
	jack->SubmitCommand();

	delete mike;
	delete lily;
	delete word1;
	delete excel1;
	delete ppt1;
	delete jack;

	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方寸间沧海桑田

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值