C++设计模式_23_Command 命令模式

我们将Command 和Visitor归为"行为变化"模式。
Command 命令模式与函数对象十分类似,但在C++主流框架中,函数对象(function object)应用的更为广泛。

文章目录

  • [1. "行为变化"模式](#1. “行为变化”模式)
    • [1.1 典型模式](#1.1 典型模式)
  • [2. 动机( Motivation )](#2. 动机( Motivation ))
  • [3. 模式定义](#3. 模式定义)
  • [4. Command 命令模式代码演示](#4. Command 命令模式代码演示)
  • [5. 结构( Structure )](#5. 结构( Structure ))
  • [6. 要点总结](#6. 要点总结)
  • [7. 其他参考](#7. 其他参考)

1. "行为变化"模式

  • 在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。"行为变化"模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合

行为一般是一段代码,代码和对象之间的绑定关系是极其紧密的,是一种天然的编译式绑定。类的成员函数分为两类,一类是虚函数,一类是非虚的或者静态函数,第二类函数是地址编译时静态的绑定,虚函数是运行时的绑定,这些绑定实际上是非常紧耦合的。"行为变化"模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合。

1.1 典型模式

  • Command

  • Visitor

2. 动机( Motivation )

  • 在软件构建过程中,"行为请求者"与"行为实现者"通常呈现一种"紧耦合"。但在某些场合一一比如需要对行为进行"记录、撤销/重(undo/redo)、事务"等处理,这种无法抵御变化的紧耦合是不合适的。

  • 在这种情况下,如何将"行为请求者"与"行为实现者"解耦 ?将一组行为抽象为对象,可以实现二者之间的松耦合。

3. 模式定义

将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

----《设计模式》GoF

4. Command 命令模式代码演示

整体代码:

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
using namespace std;


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

class ConcreteCommand1 : public Command
{
    string arg;
public:
    ConcreteCommand1(const string & a) : arg(a) {}
    void execute() override
    {
        cout<< "#1 process..."<<arg<<endl;
    }
};

class ConcreteCommand2 : public Command
{
    string arg;
public:
    ConcreteCommand2(const string & a) : arg(a) {}
    void execute() override
    {
        cout<< "#2 process..."<<arg<<endl;
    }
};
        
        
class MacroCommand : public Command
{
    vector<Command*> commands;
public:
    void addCommand(Command *c) { commands.push_back(c); }
    void execute() override
    {
        for (auto &c : commands)
        {
            c->execute();
        }
    }
};
        

        
int main()
{

    ConcreteCommand1 command1(receiver, "Arg ###");
    ConcreteCommand2 command2(receiver, "Arg $$$");
    
    MacroCommand macro;
    macro.addCommand(&command1);
    macro.addCommand(&command2);
    
    macro.execute();

}

代码分析:

Command类中塞了一个execute()虚函数

cpp 复制代码
class Command
{
public:
    virtual void execute() = 0;
};

继承自Command的类构成的对象,表达的就是一个行为对象,如下面所展示的ConcreteCommand1,封装了请求处理过程的参数信息,override execute()函数,类似的有ConcreteCommand2类。

cpp 复制代码
class ConcreteCommand1 : public Command
{
    string arg;
public:
    ConcreteCommand1(const string & a) : arg(a) {}
    void execute() override
    {
        cout<< "#1 process..."<<arg<<endl;
    }
};

我们还可以用一些宏命令,可以把多个命令组合,vector<Command*> commands;使用了类似composite的设计模式,组合命令本身继承自命令对象,实现execute() ,遍历的方式对vector<Command*> commands;所有的子节点进行遍历操作

cpp 复制代码
class MacroCommand : public Command
{
    vector<Command*> commands;
public:
    void addCommand(Command *c) { commands.push_back(c); }
    void execute() override
    {
        for (auto &c : commands)
        {
            c->execute();
        }
    }
};

下面代码ConcreteCommand1 command1(receiver, "Arg ###");ConcreteCommand2 command2(receiver, "Arg $$$");MacroCommand macro;封装出来了command对象。下面的方式还有很多其他好处,一个macro组合命令可以包含多个子命令,最关键的是从此拿着command1、command2、macro,这些都是对象,但又表征的是行为(只有execute()一个函数)。当一个对象表征一个行为,你拿着这个对象可以当做参数来传递,当做字段存储,序列化,存在某些数组结构里等等,这就是所谓灵活化,背后表征的实际是一段代码,这个代码是带有丰富的参数信息。

cpp 复制代码
int main()
{

    ConcreteCommand1 command1(receiver, "Arg ###"); //构成命令
    ConcreteCommand2 command2(receiver, "Arg $$$"); //构成命令
    
    MacroCommand macro; //构成命令组
    macro.addCommand(&command1);
    macro.addCommand(&command2);
    
    macro.execute();//执行

}

5. 结构( Structure )

上图是《设计模式》GoF中定义的Command 命令模式的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。

Command是抽象的命令接口,ConcreteCommand是一些变化的。比如在实现编辑器上的命令,剪切,删除,粘贴等都可以实现成命令对象,之后就可以对这些存储在一个栈结构里,实现redo/undo操作。这就是一旦把行为对象话之后可能获得的好处。

真正要实现redo/undo或者特殊的针对对象的处理逻辑,那就是另外的业务逻辑,可能复杂也可能简单,基本思想要把行为封装为对象

6. 要点总结

  • Command模式的根本目的在于将"行为请求者"与"行为实现者"解耦,在面向对象语言中,常见的实现手段是"将行为抽象为对象"

  • 实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。通过使用Composite模式,可以将多个命封装为一个"复合命令"MacroCommand。

  • Command模式与C++中的函数对象有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的"接口-实现"来定义行为接口规范,更严格,但有性能损失;C++函数对象以函数签名来定义行为接口规范,更灵活,性能更高。

94年定义Command模式的时候,C++函数对象并没有广为使用,泛型编程是98年,后来人们发现Command模式与C++中的函数对象十分类似,在某些场合C++函数对象要优于Command模式。

Command以面向对象中的"接口-实现"来定义行为接口规范virtual void execute() = 0;,函数是execute()、返回值为void,参数是空,继承具体时候必须遵守。由于是虚函数,会有性能损失。

C++函数对象以函数签名来定义行为接口规范 :尤其是使用模板之后,只需要参数和返回值一致即可,名字无所谓,所以更为灵活,接口规范是隐式的。由于利用了模板是编译时多态技术,Command中是运行时虚函数动态遍析 for (auto &c : commands) { c->execute(); }

基于前面提到的2个区别,在C++主流框架中,函数对象(function object)应用的更为广泛(在C++的所有设计中性能优先),但是Command这种思想在其他语言得到了极大的应用(与interator的应用情况类似)

C++函数对象是利用了C++非常好的特征:可以重载括号;调用操作符;和泛型编程结合在一起,很多时候使用的是模板的编译式绑定,而此处使用的是运行时绑定。

7. 其他参考

C++设计模式------命令模式

相关推荐
南东山人3 小时前
一文说清:C和C++混合编程
c语言·c++
Ysjt | 深6 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__6 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word7 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆7 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz7 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE7 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy8 小时前
c++ 笔记
开发语言·c++
fengbizhe8 小时前
笔试-笔记2
c++·笔记