设计模式—— 十五 :命令模式
@
什么是命令模式?
命令模式的定义:
Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。) |
命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送 者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法。
命令模式通用类图如下:
命令模式包含这么几个角色:
- Command(抽象命令):声明需要执行的命令
- ConcreteCommand(具体命令):实现声明的命令
- Receive(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理
- Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。
命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式通用代码如下:
- Receiver:Receiver类实现具体的业务需求
/**
* @author 三分恶
* @date 2020年6月28日
* @description 接收者
*/
public class Receiver {
public void doSomething() {
//具体的业务逻辑
}
}
- 抽象的Command类:
/**
* @author 三分恶
* @date 2020年6月28日
* @description 抽象的Command类
*/
public abstract class Command {
// 每个命令类都必须有一个执行命令的方法
public abstract void execute();
}
- 具体的Command类:根据需求,具体的命令类可以有多个:
/**
* @author 三分恶
* @date 2020年6月28日
* @description 具体的命令类
*/
public class ConcreteCommand extends Command {
// 维持一个对请求接收者对象的引用
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
public void execute() {
// 调用请求接收者的业务处理方法doSomething()
receiver.doSomething();
}
}
- 调用者Invoker类:
/**
* @author 三分恶
* @date 2020年6月28日
* @description 调用者
*/
public class Invoker {
private Command command;
// 设值注入
public void setCommand(Command command) {
this.command = command;
}
// 执行命令
public void action() {
this.command.execute();
}
}
- 场景类:
/**
* @author 三分恶
* @date 2020年6月28日
* @description
*/
public class Client {
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义接收者
Receiver receiver = new Receiver();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand(receiver);
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}
为什么要用命令模式?
假设有一个这样的业务场景:一个正在开发的软件项目,分为三个组:需求组、美工组、开发组。在开发的过程中,客户需要和厂商沟通,和需求组讨论需求、和 美工讨论页面、和代码组讨论实现,告诉他们修改、删除、增加各种内容等。
使用命令模式前
上面的业务场景抽象成类图:
根据类图进行具体的编码实现:
- Group:抽象类,定义业务:
/**
* @author 三分恶
* @date 2020年6月28日
* @description 抽象Group
*/
public abstract class Group {
// 甲乙双方分开办公,如果甲方要和某个组讨论,首先要找到这个组
public abstract void find();
// 要求增加功能
public abstract void add();
// 要求删除功能
public abstract void delete();
// 要求修改功能
public abstract void change();
// 要求给出所有的变更计划
public abstract void plan();
}
- RequirementGroup(需求组):
public class RequirementGroup extends Group{
@Override
public void find() {
System.out.println("找到需求组...");
}
@Override
public void add() {
System.out.println("客户要求增加一项需求...");
}
@Override
public void delete() {
System.out.println("客户要求删除一项需求...");
}
@Override
public void change() {
System.out.println("客户要求修改一项需求...");
}
@Override
public void plan() {
System.out.println("客户要求需求变更计划...");
}
}
- PageGroup(美工组):
public class PageGroup extends Group{
@Override
public void find() {
System.out.println("找到美工组...");
}
@Override
public void add() {
System.out.println("客户要求增加一个页面...");
}
@Override
public void delete() {
System.out.println("客户要求删除一个页面...");
}
@Override
public void change() {
System.out.println("客户要求改变一个页面...");
}
@Override
public void plan() {
System.out.println("客户要求页面变更计划...");
}
}
- CodeGroup(开发组):
public class CodeGroup extends Group{
@Override
public void find() {
System.out.println("找到代码组...");
}
@Override
public void add() {
System.out.println("客户要求增加一项功能...");
}
@Override
public void delete() {
System.out.println("客户要求删除一项功能...");
}
@Override
public void change() {
System.out.println("客户要求修改一项功能...");
}
@Override
public void plan() {
System.out.println("客户要求代码变更计划...");
}
}
- 场景类:
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
//首先客户找到需求组说,过来谈需求,并修改
System.out.println("-----------客户要求增加一项需求---------------");
Group rg = new RequirementGroup();
//找到需求组
rg.find();
//增加一个需求
rg.add();
//要求变更计划
rg.plan();
}
}
运行结果:
场景类中客户找到了需求组,向需求组提出了增加需求和需求变更的要求。
这样存在什么问题呢?甲方对乙方做一些要求的时候,要找到对应的组,但是不能希望甲方是非常专业的,可能提需求提到了开发组头上,要求页面变更提到了需求组头上。
使用命令模式后
这是为什么项目需要项目经理的原因之一。
甲方不管你什么需求、美工、开发,只管把项目经理叫过去,告诉项目经理他们的要求。项目经理再根据甲方的要求向各个组下达命令。对应的,就可以引入命令模式。
来看看具体的代码:
- 抽象命令类:
public abstract class Command {
//把三个组都定义好,子类可以直接使用
protected RequirementGroup rg = new RequirementGroup(); //需求组
protected PageGroup pg = new PageGroup(); //美工组
protected CodeGroup cg = new CodeGroup(); //代码组
//只有一个方法,你要我做什么事情
public abstract void execute();
}
- 增加需求的命令:
public class AddRequirementCommand extends Command{
@Override
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//给出计划
super.rg.plan();
}
}
- 删除页面的命令:
public class DeletePageCommand extends Command{
@Override
public void execute() {
//找到页面组
super.pg.find();
//删除一个页面
super.rg.delete();
//给出计划
super.rg.plan();
}
}
- 负责人Invoker:负责人只要接到客户的命令,就立刻执行。
public class Invoker {
// 什么命令
private Command command;
// 客户发出命令
public void setCommand(Command command) {
this.command = command;
}
// 执行客户的命令
public void action() {
this.command.execute();
}
}
- 场景类:模拟客户增加需求的命令。
public class Client {
public static void main(String[] args) {
// 定义调用者
Invoker jingli = new Invoker();
// 客户要求增加一项需求
System.out.println("------------客户要求增加一项需求---------------");
// 客户下命令
Command command = new AddRequirementCommand();
// 调用者接收到命令
jingli.setCommand(command);
// 调用者执行命令
jingli.action();
}
}
场景类就简单了很多,如果客户提出其它要求,如删除页面,只需要:
// 客户下命令
//Command command = new AddRequirementCommand();
Command command = new DeletePageCommand();
同样比较简单。
这样一来,客户提要求的时候,不需要知道具体谁去完成这个要求,只需求把这个要求提给项目经理即可。
高内聚的要求就被满足了。
命令模式优缺点
优点
● 类间解耦
调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command 抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
● 可扩展性 Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严 重的代码耦合。
缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调 用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这 将影响命令模式的使用。
命令模式使用场景
在以下情况下可以考虑使用命令模式:
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者 无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调 用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍 然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可 以通过请求日志文件等机制来具体实现。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
参考:
【1】:《Java设计模式之禅》
【2】:《design-pattern-java》
【3】:《研磨设计模式》