命令模式(Command)

命令模式(Command)

命令模式(Command)[Action/Transaction]

意图:将一个请求封装为一个对象,从而可用不同的请求对客户参数化。对请求排队或记录请求日志,以及支持可撤消的操作。

应用:用户操作日志、撤销恢复操作。

模式结构

心得

命令对象的抽象接口(Command)提供的两个常见操作——执行和撤销,其他的命令对象要实现这个接口。命令模式使之上是将调用对象(Invoker)与被调用对象(Image、Text)之间的耦合关系解除。真正调用对象操作的是具体实现的命令对象,它把具体操作封装在execute内部,并为之实现了逆向的操作(如果可以的话)。而原先的调用者(invoker)只需要执行命令对象(Command)的execute和unExecute操作即可,而并不知道它操作了什么样的具体对象。使用Command将对象间的调用耦合关系解除,同时也获得了用户操作的历史信息,把这些信息记录在一个队列内部,通过顺序访问队列重复执行unExecute和excute操作即可完成撤销和恢复的功能。原则上撤销和执行的操作是相互抵消的,但是有时这并不能精确保证,为了解决类似问题可以使用备忘录模式精确记录对象状态。另外,在命令对象继承层次中引入组合模式可以实现宏命令的功能。

举例

为了方便理解,我们把调用者(invoker)看作GUI的控件对象,用户操作空间要改变图片的大小的时候会通过多态性质调用ResizeCommand的execute操作,而该execute会执行Image的resize操作。而用户要想撤销该操作,只需要访问命令队列,执行该对象的unExecute操作即可。具体的C++实现代码如下:

//具体用户操作的对象
class Image
{
public:
 void resize()
    {
        cout<<"改变图片大小"<<endl;
    }
};
class Text
{
public:
 void changeColor()
    {
        cout<<"改变文字颜色"<<endl;
    }
};
//命令
class Command
{
public:
 virtual void execute()=0;
 virtual void unExecute()=0;
 virtual ~Command(){};
};
class ResizeCommand:public Command
{
    Image img;
public:
 virtual void execute()
    {
        img.resize();
    }
 virtual void unExecute()
    {
        cout<<"恢复图片大小"<<endl;
    }
};
class ColorCommand:public Command
{
    Text txt;
public:
 virtual void execute()
    {
        txt.changeColor();
    }
 virtual void unExecute()
    {
        cout<<"恢复文字颜色"<<endl;
    }
};
//命令队列
class CmdList
{
 static const unsigned int maxLen=20;
    vector<Command*>cmds;
 int curPos;//执行点——记录卡刚执行命令在队列的位置
public:
    CmdList()
    {
        curPos=-1;
    }
 void storeCmd(Command*cmd)
    {
 //从执行点往后的元素先删除
 int times=cmds.size()-curPos-1;//执行点后边元素个数
 while(times--)
        {
            delete cmds.back();//清除内存
            cmds.pop_back();
        }
 //压入新命令
        cmds.push_back(cmd);
 if(cmds.size()>maxLen)//超过了一个元素,删除第一个
        {
            cmds.erase(cmds.begin());
        }
        curPos=cmds.size()-1;//插入新的元素即刚执行过,记录位置
    }
 void unDo()//撤销
    {
 if(curPos>=0)//有效位置
        {
            cmds[curPos--]->unExecute();
        }
    }
 void reDo()//恢复
    {
 if(curPos<(int)cmds.size()-1)//后边有节点才能恢复
        {
            cmds[++curPos]->execute();
        }
    }
    ~CmdList()
    {
 for(vector<Command*>::iterator it=cmds.begin();it!=cmds.end();++it)
        {
            delete *it;
        }
        cmds.clear();
    }
};
//调用者
class Invoker
{
public:
 void operation(Command*cmd)
    {
        cmd->execute();
    }
};

而用户使用该模式需要的是构造合适的命令对象,发送到调用者那里就可以了。在命令成功执行后需要将该命令对象存储到命令队列去,这里根据实际需求决定存储命令的调用位置。上述代码中顺便实现了队列的撤销和恢复操作,其关键在于维护一个执行点curPos,记录当前执行过的命令对象所在的位置,当然这些对用户是不可见的。

参考文章http://www.tracefact.net/Design-Pattern/Command.aspx

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大前端_Web

从前端模块化的概念来理解Webpack

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

1763
来自专栏性能与架构

Zookeeper实例 - 分布式锁

需求场景 在分布式系统中,通常会有多个子系统需要操作同一资源,例如修改数据存储中的某一数据 这些子系统各自独立,操作共享资源时没有逻辑顺序,有可能会出现同时...

3705
来自专栏Web项目聚集地

通俗易懂讲解Java线程安全

一个非科班的技术男,自学半年找到了份不错的程序员工作,运营维护订阅号「一个程序员的成长」希望做一个专注于Java领域的公众号,喜欢本文章可以搜索关注。

1672
来自专栏决胜机器学习

​PHP开发过程的那些坑(五) ——PHP的empty()

PHP开发过程的那些坑(五)——PHP的empty() (原创内容,转载请注明来源,谢谢) 一、遇到的问题 PHP的empty不是一个函数,而是一个语言结构,用...

3246
来自专栏linux系统运维

原 shell脚本中的逻辑判断,文件目录属

2805
来自专栏佳爷的后花媛

路由&模块化设计&命名空间

ThinkPHP采用模块化的架构思想,可以支持多模块应用的创建,让应用的扩展更加方便. 先简单说下路由规则:

2053
来自专栏追不上乌龟的兔子

Python3.7的新API:asyncio.run()

Python3.7的正式版本已经发布有一段时间了,出了内置的breakpoint()断点函数,颇受争议的dataclass,自定义模块里的__getattr__...

1.8K6
来自专栏marsggbo

python3编码问题终结者--还搞不懂你来找我

python unicode bytes str 编码 首先需要说明一下,该篇文章是以python3为基础的,python2是否适合没有验证过。 由于pytho...

7658
来自专栏有趣的Python和你

Flask学习笔记之url和函数映射

这是flask的第二篇文章,在第一篇文章中,我们看到了flask是如何让固定url和和函数保证对应的,但现实的URL中,URL路径是多变的,今天我们就来学习详细...

1462
来自专栏互联网大杂烩

设计模式(一)

确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。 优点:减少系统开销,避免对资源多重利用。 缺点:没有接口,不利于扩展。

882

扫码关注云+社区

领取腾讯云代金券