定义:
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能
示例一:命令模式(通用版)
1. 类图15-4
2. 类图说明
① Receive 接收者角色。该角色就是干活的角色,命令传递到这里是应该被执行的。
② Command 命令角色。需要执行的所有命令都在这里声明。
③ Invoker 调用者角色。接收到命令,并执行命令。
3. 代码清单15-1:
********** 1. 通用命令模式,代码清单15-1:***************//
//通用Receiver
class Receiver
{
public:
virtual void doSomething() = 0;
};
//具体Receiver
class ConcreteReciver1:public Receiver
{
public:
virtual void doSomething()
{
qDebug() << "ConcreteReciver1 doSomething";
}
};
//抽象Command
class Command
{
public:
virtual void execute() = 0;
};
class ConcreteCommand1:public Command
{
public:
ConcreteCommand1(Receiver *receiver)
{
this->m_receiver = receiver;
}
virtual void execute()
{
this->m_receiver->doSomething();
}
private:
Receiver *m_receiver;
};
class ConcreteCommand2:public Command
{
public:
ConcreteCommand2(Receiver *receiver)
{
this->m_receiver = receiver;
}
virtual void execute()
{
this->m_receiver->doSomething();
}
private:
Receiver *m_receiver;
};
//调用者
class Invoker
{
public:
void setCommand(Command *command)
{
this->m_command = command;
}
void action()
{
this->m_command->execute();
}
private:
Command *m_command;
};
int main()
{
Invoker invoker;
Receiver *receiver = new ConcreteReciver1();
Command *command = new ConcreteCommand1(receiver);
invoker.setCommand(command);
invoker.action();
return 0;
}
二、命令模式的应用
1. 优点:
- 类间解耦。调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需要调用Command 抽象类 的execute方法就可以,不需要了解到底是哪个接收者执行。
- 可扩展性。Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块不产生严重的代码耦合。
- 命令模式结合其他模式会更好。命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。
2. 缺点
在Command的子类:如果有N个命令,问题就出来了,Command的子类就不是几个,而是N个,这个类膨胀的非常大,在项目中需要慎重考虑使用。
3. 使用场景:
只要你认为是命令的地方就可以采用命令模式,例如GUI开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟DOS命令的时候,也要采用命令模式;触发-反馈机制的处理等。
示例二:项目开发过程(初步设计)
1.需求分析:
为旅行社内部建立管理系统 : 用来管理客户、旅游资源、票务以及内部事务,整体上类似于一个小型的MIS系统。
该项目的成员分工分为需求组(Requirement Group,RG)、美工组(Page Group,PG)、代码组(Code Group,CG)
2. 类图,类图15-1
3. 类图说明:
抽象中的每个方法,都是一个命令,给出命令然后由相关人员去执行。
4. 代码清单15-1:
********** 2. 项目开发过程(初步设计),代码清单15-1:***************//
//抽象组
class Group
{
public:
virtual void find() = 0; //找到某个组
virtual void add() = 0; //被要求增加功能
virtual void delete1() = 0; //被要求删除功能
virtual void change() = 0; //被要求修改功能
virtual void plan() = 0; //被要求给出所有的变更计划
};
//抽象组
class RequirementGroup:public Group
{
public:
virtual void find() {qDebug() << "find RequirementGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//美工组
class PageGroup:public Group
{
public:
virtual void find() {qDebug() << "find PageGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//代码组
class CodeGroup:public Group
{
public:
virtual void find() {qDebug() << "find CodeGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
int main()
{
//找需求组改
Group *rg = new RequirementGroup();
rg->find();
rg->add();
rg->plan();
//找美工组改
Group *pg = new PageGroup();
pg->find();
pg->delete1();
pg->plan();
return 0;
}
5. 设计问题
每次修改计划,都需要找到一个组,然后布置任务,然后出计划,需求增多时非常复杂,我们需要增加一个负责人,下面进行改善
示例三:项目开发过程(改善版)
1. 类图15-2:
2. 类图说明:
在类图上增加了一个Invoker类,其作用是根据客户的命令安排不同的组员进行工作。当客户需要再界面上删除一条记录,Invoker类接收到该 String 类型命令后,通知美工组 PageGroup 开始 delete, 然后再找到代码组 DodeGroup 后台不要存到数据库中,最后反馈给客户一个执行计划。
3. 问题:
客户的命令是一个 String 类型的,这有非常多的变化,在系统设计中,字符串没有约束力。
4. 改善方案:
对客户发出的命令进行封装,每个命令是一个对象,避免用户,负责人,组员之间的交流误差,封装后的结果是,客户说一个命令,我们的项目组就开始启动,不用思考、解析命名字符串
5. 改善后类图 15-3
6. 类图说明
Command 抽象类只有一个方法 execute,其作用就是执行命令,子类非常坚决的实现该命令。
Command 抽象类 :客户发出的命令,定义三个工作组的成员变量,供子类使用;定义一个抽象方法execute,由子类来实现
CInvoker 实现类:项目负责人,setComand 接收客户发的命令,action方法执行命令
7. 代码清单15-2:
********** 2. 项目开发过程(改善版),代码清单15-2:***************//
//抽象组
class Group
{
public:
virtual void find() = 0; //找到某个组
virtual void add() = 0; //被要求增加功能
virtual void delete1() = 0; //被要求删除功能
virtual void change() = 0; //被要求修改功能
virtual void plan() = 0; //被要求给出所有的变更计划
};
//抽象组
class RequirementGroup:public Group
{
public:
virtual void find() {qDebug() << "find RequirementGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//美工组
class PageGroup:public Group
{
public:
virtual void find() {qDebug() << "find PageGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//代码组
class CodeGroup:public Group
{
public:
virtual void find() {qDebug() << "find CodeGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//抽象命令类
class Command
{
public:
virtual void execute() = 0;
Command()
{
this->m_rg = new RequirementGroup();
this->m_pg = new PageGroup();
this->m_cg = new CodeGroup();
}
protected:
RequirementGroup *m_rg;
PageGroup *m_pg;
CodeGroup *m_cg;
};
//增加需求命令
class AddRequirementCommand:public Command
{
public:
virtual void execute()
{
this->m_rg->find();
this->m_rg->add();
this->m_rg->plan();
}
};
//删除页面命令
class DeletePageCommand:public Command
{
public:
virtual void execute()
{
this->m_pg->find();
this->m_pg->delete1();
this->m_pg->plan();
}
};
class Invoker
{
public:
void setCommand(Command *command)
{
this->m_command = command;
}
void action()
{
this->m_command->execute();
}
private:
Command *m_command;
};
int main()
{
//增加一项需求
Invoker xiao;
Command *command = new AddRequirementCommand();
xiao.setCommand(command);
xiao.action();
//删除一个页面
Invoker san;
Command *command2 = new DeletePageCommand();
san.setCommand(command2);
san.action();
return 0;
}
四、命令模式的扩展
4-1. 扩展一:增加需求
示例四:项目开发过程 (增加需求版)
1. 需求分析
接收到一个命令的时候通知每一个组
2. 修改方法
在 AddRequirementCommand 类的 execute 方法中增加对 PageGroup 和 CodePage 的调用即可
3. 修改后代码清单
class AddRequirementCommand:public Command
{
public:
virtual void execute()
{
this->m_rg->find();
this->m_rg->add();
this->m_pg->add();
this->m_cg->add();
this->m_rg->plan();
}
};
4-2. 扩展二:回退功能
示例五:项目开发过程 (增加回退功能)
1. 需求分析
发出一个命令,在没有执行或执行后撤销
2. 解决方法
①结合备忘录模式还原最后状态。适合接收者为状态的变更情况,不适合事件处理
②通过增加一个新的命令,实现事件回滚。
撤销也是一个命令,即是 Command 的一个子类,实现功能是恢复刚删除的页面。命令由页面组接收,页面组必须有一个方法恢复最后删除的页面,也就是日志回滚机制,指定一个页面,回滚回去。
3. 代码清单15-4:
// ********** 4. 扩展二(回退功能),代码清单15-4:***************//
//抽象组
class Group
{
public:
virtual void find() = 0; //找到某个组
virtual void add() = 0; //被要求增加功能
virtual void delete1() = 0; //被要求删除功能
virtual void change() = 0; //被要求修改功能
virtual void plan() = 0; //被要求给出所有的变更计划
virtual void rollBack() = 0; //根据日志进行回滚
};
//抽象组
class RequirementGroup:public Group
{
public:
virtual void find() {qDebug() << "find RequirementGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//美工组
class PageGroup:public Group
{
public:
virtual void find() {qDebug() << "find PageGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//代码组
class CodeGroup:public Group
{
public:
virtual void find() {qDebug() << "find CodeGroup";}
virtual void add() {qDebug() << "add a request";}
virtual void delete1(){qDebug() << "delete a request";}
virtual void change() {qDebug() << "change a request";}
virtual void plan() {qDebug() << "plan";}
};
//抽象命令类
class Command
{
public:
virtual void execute() = 0;
Command()
{
this->m_rg = new RequirementGroup();
this->m_pg = new PageGroup();
this->m_cg = new CodeGroup();
}
protected:
RequirementGroup *m_rg;
PageGroup *m_pg;
CodeGroup *m_cg;
};
//增加需求命令
class AddRequirementCommand:public Command
{
public:
virtual void execute()
{
this->m_rg->find();
this->m_rg->add();
this->m_rg->plan();
}
};
//删除页面命令
class DeletePageCommand:public Command
{
public:
virtual void execute()
{
this->m_pg->find();
this->m_pg->delete1();
this->m_pg->plan();
}
};
//撤销删除
class CancelDeletePageCommand:public Command
{
public:
virtual void execute()
{
this->m_pg->rollBack();
}
};
class Invoker
{
public:
void setCommand(Command *command)
{
this->m_command = command;
}
void action()
{
this->m_command->execute();
}
private:
Command *m_command;
};
int main()
{
//增加一项需求
Invoker xiao;
Command *command = new AddRequirementCommand();
xiao.setCommand(command);
xiao.action();
//删除一个页面
Invoker san;
Command *command2 = new DeletePageCommand();
san.setCommand(command2);
san.action();
return 0;
}
4. 代码分析
抽象组中增加了一个rollBack 方法,每个接收者都可以对自己实现的任务进行回滚
新增加一个命令 CancelDeletePageCommand 实现撤销
使用Invoker进行调用。
五、最佳实践
在旅行社的例子中,Receiver角色(即Group的三个实现类)没有暴露给Client, 而在通用的类图和源码中却出现了Client 对 Receiver角色的依赖。原因是每一个 模式到实际应用的时候都有一些变形,命令模式的Receiver在实际应用中都会被封装掉(除非非常必要,例如撤销处理),是因为在项目中:约定的优先级最高,每一个命令是对一个或多个 Receiver 的防撞,我们可以在项目中通过有意义的类名或命令名处理命令角色和接收者角色的耦合关系(即约定),减少高层模块(Client)对底层模块(Receiver)的依赖关系,提高系统稳定性。
建议:在实际的项目开发时采用封闭 Receiver 的方式 , 减少 Client对 Receiver 的依赖,改方案只是对 Commandd抽象类及其子类有一定修改。
示例六:通用模式(改善版)
1. 代码清单15-5
********** 5. 通用模式(改善版),代码清单15-5:***************//
//通用Receiver
class Receiver
{
public:
virtual void doSomething() = 0;
};
//具体Receiver
class ConcreteReciver1:public Receiver
{
public:
virtual void doSomething()
{
qDebug() << "ConcreteReciver1 doSomething";
}
};//具体Receiver
class ConcreteReciver2:public Receiver
{
public:
virtual void doSomething()
{
qDebug() << "ConcreteReciver2 doSomething";
}
};
//抽象Command
class Command
{
public:
Command(){}
Command(Receiver *receiver)
{
this->m_receiver = receiver;
}
virtual void execute() = 0;
protected:
Receiver *m_receiver;
};
class ConcreteCommand1:public Command
{
public:
ConcreteCommand1()
{
this->m_receiver = new ConcreteReciver1();
}
ConcreteCommand1(Receiver *receiver)
{
this->m_receiver = receiver;
}
virtual void execute()
{
this->m_receiver->doSomething();
}
private:
Receiver *m_receiver;
};
class ConcreteCommand2:public Command
{
public:
ConcreteCommand2()
{
this->m_receiver = new ConcreteReciver2();
}
ConcreteCommand2(Receiver *receiver)
{
this->m_receiver = receiver;
}
virtual void execute()
{
this->m_receiver->doSomething();
}
private:
Receiver *m_receiver;
};
//调用者
class Invoker
{
public:
void setCommand(Command *command)
{
this->m_command = command;
}
void action()
{
this->m_command->execute();
}
private:
Command *m_command;
};
int main()
{
Invoker invoker;
Command *command = new ConcreteCommand1();
invoker.setCommand(command);
invoker.action();
return 0;
}
2. 代码分析
在Command父类中声明了一个接收者,通过构造函数约定每个具体命令都必须指定接收者,根据开发场景要求可以有多个接收者时,需要用到集合类型。
参考文献《秦小波. 设计模式之禅》(第2版) (华章原创精品) 机械工业出版社