命令模式
小故事:
经过多年的努力,终于我成为了一名项目经理,现在我要带着我的团队去接活了。项目员工分为 需求组(RequirementGroup,RG)、美工组(PageGroup,PG)、代码组(CodeGroup,CG)。刚开始,客户也就是甲方,很乐意和我们每个组探讨,比如和需求组讨论需求、和美工讨论页面、和代码组讨论实现,告诉他们增删改查的各种内容等。这是比较常见的甲乙方合作模式,甲方深入到乙方的项目开发中。可以用类图表示这个过程。
这个类图很简单,客户(client)和每个组都有交流,每个组都继承抽象类 Group
package com.abel.example;
/**
* Created by yangyibo on 17/9/20.
*/
public abstract class Group {
// 甲乙双方分开办公,如果要和某个组讨论问题,要先找到这个组
public abstract void find();
//被要求增加功能
public abstract void add();
//被要求删除功能
public abstract void delete();
//被要求改变功能
public abstract void change();
//被要求设计功能
public abstract void plan();
}
可以看到抽象类中的每个方法,都是一个个的命令语气--“找到他,删除,增加,修改,给我执行计划!”都是一个个的命令,然后由相关的人员去执行。我门的三个实现类就是 执行人员。
代码如下:
/**
* Created by yangyibo on 17/9/20.
* 需求组
*/
public class RequirementGroup extends Group {
//客户要求需求组过去和他们谈
public void find() {
System.out.println("找到需求组");
}
//客户需要添加一项需求
public void add() {
System.out.println("客户要添加一项需求");
}
//客户需要删除一项需求
public void delete() {
System.out.println("客户要删除一项需求");
}
//客户需要修改一项需求
public void change() {
System.out.println("客户要修改一项需求");
}
//客户要求给出变更计划
public void plan() {
System.out.println("客户要求给出变更计划");
}
}
需求组有了,美工组也很重要,客户最终接触到的还是界面,美工组是脸面。
/**
* Created by yangyibo on 17/9/20.
* 美工组
*/
public class PageGroup extends Group {
//客户要找到美工组过去和他们谈
public void find() {
System.out.println("找到美工组");
}
//美工被要求增加一个页面
public void add() {
System.out.println("美工被要求增加一个页面");
}
//美工被要求删除一个页面
public void delete() {
System.out.println("美工被要求删除一个页面");
}
//美工被要求修改一个页面
public void change() {
System.out.println("美工被要求修改一个页面");
}
//所有的增、删、改都要给出计划
public void plan() {
System.out.println("要求美工组所有的增、删、改都要给出计划");
}
}
代码组
/**
* Created by yangyibo on 17/10/17.
* 码农组
*/
public class CodeGroup extends Group {
//客户要求需求组过去和他们谈
public void find() {
System.out.println("找到代码组...");
}
//客户要求增加一项功能
public void add() {
System.out.println("客户要求增加一项功能");
}
//客户要求删除一项功能
public void delete() {
System.out.println("客户要求删除一项功能");
}
//客户要求修改一项功能
public void change() {
System.out.println("客户要求修改一项功能");
}
//客户要求给出变更计划
public void plan() {
System.out.println("客户要求给出变更计划");
}
}
好了项目的组织结构都产生了,看客户怎么和我们谈。客户看了需求组写的分析说明书,要求增加需求,场景如下:
/**
* Created by yangyibo on 17/10/17.
* 场景类
*/
public class Client {
public static void main(String[] args) {
System.out.println("-----------客户要求增加一项需求---------------");
Group rg = new RequirementGroup();
//找到需求组
rg.find();
//添加一项需求
rg.add();
//给出变更计划
rg.plan();
//客户的需求暂时满足了,过了一段时间客户要求新增一个界面。
System.out.println("\n\n-----------过了一天客户要删除一个页面---------------");
Group pg = new PageGroup();
pg.find();
pg.delete();
pg.plan();
}
}
好了需求改过了,页面也改过了,应该没什么问题了吧,过了一天后,客户又让代码组过去,说数据库设计问题,然后又叫美工组过去,布置一堆命令。。。。。问题来了,我门可以修改,但是每次都叫一个组过去,布置个任务,然后出计划,每次都这样。终于客户烦了,跟我说:“我不管你们内部怎么安排,你给我找个接头人,我只用给接头人发布命令就好了。”
我一听,好了,这个也正是我想要的。项目组的兄弟们也已经受不了了,于是我改变了一下我的处理方式。
在原有的类图上增加一个Invoker 类,用于根据命令,安排不同员工进行工作。但是客户的命令是一句话,是个String类型,这有非常多的变化,仅仅通过一个字符串来传递命令并不是一个非常好的方案,因为系统设计里字符串没有约束力。根据字符串判断业务逻辑不是一个优秀的解决方案。好的解决方案是:对客户发出的命令进行封装,每个命令是一个对象,避免 交流误差。封装后的结果就是,客户只用说一个命令,项目组就开始有条不紊的执行起来。如图
Command 抽象类客户发给我们的命令,只有一个execute方法,由子类实现。作用是执行命令,负责人Invoker 接收到命令后,立刻执行Command的execute方法。
/**
* Created by yangyibo on 17/10/18.
*/
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 {
public void execute() {
//找到需求组
super.rg.find();
//添加需求
super.rg.add();
//给出计划
super.rg.plan();
}
}
删除页面的命令
public class DeletePageCommand extends Command {
public void execute() {
// 找到美工组
super.pg.find();
//删除一个页面
super.pg.delete();
// 给出计划
super.pg.plan();
}
}
command 抽象类可以有N个子类,如果增加一个功能命令,如增加或删除一个需求等,只要是客户产生且时常性的都可以定义为一个命令。
命令确定下来了,我门再看一下负责人Invoker
Invoker实现类,项目接头负责任,负责接收命令,action方法是执行客户的命令
public class Invoker {
// 客户发送的命令
private Command command;
public Command getCommand() {
return command;
}
public void setCommand(Command command) {
this.command = command;
}
// 执行客户的命令
public void action(){
this.command.execute();
}
}
好了改造完毕了,现在接到客户的命令,就立刻执行了,我门模拟增加一项需求的过程。
/**
* Created by yangyibo on 17/10/18.
*/
public class Client {
public static void main(String[] args) {
// 定义接头人 sb
Invoker sb = new Invoker();
System.out.println("------------客户要求增加一个需求---------------");
//客户下命令
//删除页面命令
Command command = new DeletePageCommand();
//sb收到命令
sb.setCommand(command);
//sb 执行命令
sb.action();
}
运行结果
------------客户要求增加一个需求---------------
找到美工组
美工被要求删除一个页面
要求美工组所有的增、删、改都要给出计划
命令模式定义
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对亲求排队或者纪录请求日志,可以提供命令的撤销和恢复功能。
Receive 接收者角色
该角色就是干活的角色,命令传递到这里是应该被执行的,具体到我们上面的小故事就是指需求组、代码组、美工组三个 Group 的实现类。
Command 命令角色
需要执行所有的命令都在这里声明。
Invoker调用者角色
收到命令,执行命令。在上面的小故事中,项目经理就是调用者。
命令模式比较简单,但是在项目中非常频繁的使用,因为它的封装性非常好,把请求方(Invoker)和执行方(Receiver)分开了,扩展性也有很好的保障,通用代码也比较简单。如下
通用Receiver类
public abstract class Receiver {
// 抽象接受者,定义每个接收者都必须完成的业务
public abstract void doSomething();
}
Receiver是一个抽象类,因为接收者可以有多个,多个就需要定义一个特性的抽象集合。
public class ConcreteReciver1 extends Receiver {
//每个接收者都必须处理一定的业务逻辑
public void doSomething() {
System.out.println("执行者1 ,执行命令");
}
}
public class ConcreteReciver2 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething() {
System.out.println("执行者2 ,执行命令");
}
}
Receiver可以是N个,这要依赖业务定义,命令角色,是命令模式的核心。
Command
/**
* Created by yangyibo on 17/10/18.
*/
public abstract class Command {
// 每个命令类都必须有一个执行命令的方法
public abstract void execute();
}
根据环境的需求,具体的命令类也有多个,读者可以再实际中扩展命令类,可以通过构造函数定义该命令是针对哪一个接收者。
public class ConcreteCommand1 extends Command {
//对 receiver 类命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand1(Receiver _receiver) {
this.receiver = _receiver;
}
public void execute() {
//业务处理
this.receiver.doSomething();
System.out.println("执行命令1");
}
}
public class ConcreteCommand2 extends Command {
//对 receiver 类命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand2(Receiver _receiver) {
this.receiver = _receiver;
}
public void execute() {
//业务处理
this.receiver.doSomething();
System.out.println("执行命令2");
}
}
调用者非常简单,只是实现命令的传递
public class Invoker {
private Command command;
public Command getCommand() {
return command;
}
public void setCommand(Command command) {
this.command = command;
}
public void action() {
System.out.println("开始执行命令");
this.command.execute();
System.out.println("命令执行结束");
}
}
场景类如下:
public class Client {
public static void main(String[] args) {
// 指定接头人
Invoker invoker = new Invoker();
// 指定执行者
Receiver receiver = new ConcreteReciver1();
// 发布命令
Command command = new ConcreteCommand1(receiver);
// 执行
invoker.setCommand(command);
invoker.action();
}
}
命令模式的优点
类间解耦
调用者与接收者之间没有任何的依赖关系,调用者实现功能时只需嗲用Command的execute方法。不用了解时那个接收者执行。
可扩展性
Command的子类可以非常容易的扩展,而调用者Invoker和高层次的模块Client不产生严重代码耦合。
命令模式结合其他模式会更优秀
命令模式可以结合责任链模式,实现命令族解析任务,结合模版方法模式可以减少Command子类膨胀问题。
命令模式缺点
Command子类膨胀问题。如果有N个命令,就会有N个Command子类。
命令模式使用场景
只要认为是命令的地方就可以采用命令模式,例如模拟DOS命令的时候,触发反馈机制的处理等。
本文摘引自《设计模式之蝉(第二版)》