一、前言

什么是命令模式?

在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合,这就是命令模式(Command Pattern)——摘自百度百科

它解决了什么问题?

解除行为请求者和行为实现者的高耦合。

比如,有一个业务,最开始的时候是:A->B

然后,有一天产品说,B这个资源比较敏感,每次调用B之前要先记录一下日志,假如说记录日志的业务逻辑是C,然后业务逻辑就会变成下面这个样子:

A -> C, A-> B

再然后,产品又说,B这个资源啊,有点不稳定,如果调用失败的时候,能不能重试3次?然后业务逻辑就变成这个样子:

A->C, retry 3 :A->B

后来,产品觉得B资源...,不这次不是B资源了,出现了一个新需要,处理的逻辑和B有点像,能不能$%^&?

结果,那块代码就变得无法维护了。。。如果有一天,需要去重构它,或者碰到一个类似的需求的时候,如何不让自己难受,不给别人挖坑?

命令模式买不了吃亏,买不了上当!

二、以餐厅点餐,了解命令模式

举个例子:假如你去一个餐厅吃烤鱼,从叫服务员点菜,到服务员通知师傅做菜,到最后服务员上菜,这个流程就可以描述成一个命令模式,类图如下:

每个角色和命令模式的几个核心要素对应关系如上图所示。

Customer(Client)

/**
* 顾客(Client)
*/
public class Customer { public static void main(String[] args) { Order order = new BakeFishOrder(new Chef("李")); Waiter waiter = new Waiter();
waiter.takeOrder(order);
waiter.orderUp();
}
}

顾客需要做3个事情:

1,点一个烤鱼

2,把菜单交给服务员

3,服务员上菜

顾客不关心这个烤鱼的制作流程,只希望,我点菜以后,能够按时把菜上上来就OK了,在实际的业务中,甚至可以把 new Chef() 这个操作也放在其他地方实现

比如具体的Command里面,或者Invoker里面,让用户感知不到,这样业务的逻辑会变得更加平滑,总的来说就是:只关注自己需要的东西

Waiter(Invoker)

/**
* 服务员(Invoker)
*/
public class Waiter { private Order order; public void takeOrder(Order order){
System.out.println("服务员接到订单!");
this.order = order;
} public void orderUp(){
System.out.println("服务员呼叫后台准备烹饪订单中的菜品");
order.execute();
System.out.println("订单菜品已经烹饪完成,服务员上菜!");
}
}

服务员这里需要做两件事情:

1,接单

2,通知厨房做菜并上菜

对于服务员来说,他也不需要关心这道菜谁做的,怎么做的,只需要给厨房说了以后,过段时间把做好的菜送到客户的手上就OK了

Order(Command)

/**
* 订单(Command)
*/
public interface Order {
void execute();
}

订单是一个抽象的东西,具体干活的是订单接口实现类,引入订单的作用是面向抽象编程,以后每加一种菜,只需要增加一个实现类就可以了

BakeFishOrder(ConncereteCommand)

/**
* 烤鱼订单(具体的订单对象)
*/
public class BakeFishOrder implements Order{ private Chef chef; public BakeFishOrder(Chef chef){
this.chef = chef;
} public void execute() {
chef.bakeFish();
}
}

烤鱼订单这里就是一个具体的订单,在它的execute方法中就需要去调用真正的实现者的一个或一些方法来实现制作烤鱼这样的一个需求。

Chef(Receiver)

/**
* 厨师(Receiver)
*/
public class Chef { private String name; public Chef(String name){
this.name = name;
} public void bakeFish(){
System.out.println(name + "师傅在制作烤鱼...");
}
}

在这个例子中,厨师就是最后干活的对应命令模式中的Receiver,绕这么大一圈,也就是为了顾客(行为请求者)和 厨师(行为实现者)的解耦。

运行结果:

顾客和厨师解耦的好处是什么?

1,在点餐的流程不变的情况下,可以随意增加新菜(Order的实现类)

2,在点餐的流程不变的情况下,原菜品的制作方式变化,上层是无需做任何修改的

3,在Waiter中,可以做各种日志记录,订单排队,之类的各种操作,非耦合的情况下,可操作性很大,并且不会影响到顾客和厨师相关类

三、遥控器需求

HeadFirst上,需要实现的是一个遥控器,遥控器用来遥控房间内的各种家电,并且可以实现undo功能。

但是有几个问题:

1,每个厂商的驱动标准是不一样的,比如电灯,开关的方法是:on(), off(),吊扇的方法是:high(), medium(),low(),off()

2,遥控器直接调用具体的家电驱动的话,一旦新的厂商加载到遥控器上面的时候,就必须要改代码,比如:电灯的开关方法变成了:dim(level),通过level来调节灯泡的明暗程度,调到0就是关,调到100就是开。

如何使用命令模式来实现呢?具体的流程如下:

0,驱动类

/**
* 描述:灯
*/
public class Light { public static final int LIGHT_ON = 100;//打开灯时,灯的亮度
public static final int LIGHT_OFF = 0;//关闭灯时,灯的亮度 private String location;//灯的位置
private int level;//亮度 public Light(String location) {
this.location = location;
} //开灯
public void on() {
level = LIGHT_ON;
System.out.println(location + ", 灯已经打开!");
} //关灯
public void off() {
level = LIGHT_OFF;
System.out.println(location + ", 灯已经关闭!");
} /**
* 调节灯的亮度
* @param level 亮度
*/
public void dim(int level) {
this.level = level; if (level == LIGHT_OFF) {
off();
return;
} if(level == LIGHT_ON){
on();
return;
} throw new RuntimeException("无法调节到指定的亮度, level: " + level);
} public int getLevel() {
return level;
}
}
/**
* 描述:吊扇
*/
public class CeilingFan { public static final int HIGH = 3;//3档
public static final int MEDIUM = 2;//2档
public static final int LOW = 1;//1档
public static final int OFF = 0;//0档
String location;//吊扇位置
private int speed;//当前速度 public CeilingFan(String location) {
this.location = location;
speed = OFF;
} public void high() {
speed = HIGH;
System.out.println(location + ",吊扇档位:" + HIGH);
} public void medium() {
speed = MEDIUM;
System.out.println(location + ",吊扇档位:" + MEDIUM);
} public void low() {
speed = LOW;
System.out.println(location + ",吊扇档位:" + LOW);
} public void off() {
speed = OFF;
System.out.println(location + ",吊扇档位:" + OFF);
} public int getSpeed() {
return speed;
}
}

1,制定标准,每个家电都必须依照这个规范来进行开发,接入遥控器系统(Command接口)

/**
* 描述:命令接口
*/
public interface Command {
/**
* 描述:执行命令
*/
void execute(); /**
* 描述:撤销命令
*/
void undo();
}

2,根据需求先实现遥控器,再实现具体的命令(由上而下开发)

/**
* 遥控器
*/
public class RemoteControl {
Command[] onCommands;//启动命令
Command[] offCommands;//关闭命令
Command undoCommand;//撤销命令 public RemoteControl() {
//初始化遥控器,命令设置为默认实现类NoCommand,减少判断逻辑,减少NLP概率
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for(int i=0;i<7;i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
} /**
* 设置命令
* @param slot 按钮位置
* @param onCommand 启动命令
* @param offCommand 关闭命令
*/
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
} /**
* 启动按钮按下时的操作
* @param slot 按钮位置
*/
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
} /**
* 关闭按钮按下时的操作
* @param slot 按钮位置
*/
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
} /**
* 撤销按钮按下时的操作
*/
public void undoButtonWasPushed() {
undoCommand.undo();
} public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ 遥控器 -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[位置 " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
stringBuff.append("[撤销] " + undoCommand.getClass().getName() + "\n");
return stringBuff.toString();
}
}
/**
* 描述:命令的默认的空实现
*/
public class NoCommand implements Command {
public void execute() { }
public void undo() { }
}

3,实现电灯的开关

/**
* 描述:开灯命令
*/
public class LightOnCommand implements Command {
private Light light;
private int lastLightLevel;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
lastLightLevel = light.getLevel();
light.on();
} public void undo() {
light.dim(lastLightLevel);
}
}
/**
* 描述:关灯命令
*/
public class LightOffCommand implements Command { private Light light;
private int lastLightLevel; public LightOffCommand(Light light) {
this.light = light;
} public void execute() {
lastLightLevel = light.getLevel();
light.off();
} public void undo() {
light.dim(lastLightLevel);
}
}

4,实现风扇的各个档位

public class CeilingFanHighCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed; public CeilingFanHighCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
} public void execute() {
prevSpeed = ceilingFan.getSpeed();
ceilingFan.high();
} public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
}
}
public class CeilingFanMediumCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed; public CeilingFanMediumCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
} public void execute() {
prevSpeed = ceilingFan.getSpeed();
ceilingFan.medium();
} public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
}
}
public class CeilingFanLowCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed; public CeilingFanLowCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
} public void execute() {
prevSpeed = ceilingFan.getSpeed();
ceilingFan.low();
} public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
}
}
public class CeilingFanOffCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed; public CeilingFanOffCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
} public void execute() {
prevSpeed = ceilingFan.getSpeed();
ceilingFan.off();
} public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
}
}

当然这种并不是最好的方式,undo中的代码被写了几次,这样,哪里有重复,哪里就有抽象(实际业务中,请根据具体业务选择是否抽象,如果抽象了发现业务更难做了,就要考虑是不是抽象的方向有问题了)

public abstract class AbstractCeilingFanCommand implements Command{
private CeilingFan ceilingFan;
private int prevSpeed; public AbstractCeilingFanCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
} public void execute() {
prevSpeed = ceilingFan.getSpeed();
doExecute();
} protected abstract void doExecute(); public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
} protected CeilingFan getCeilingFan() {
return ceilingFan;
}
}

重新实现各个档位

public class CeilingFanHighCommand extends AbstractCeilingFanCommand{

    public CeilingFanHighCommand(CeilingFan ceilingFan) {
super(ceilingFan);
} protected void doExecute() {
getCeilingFan().high();
}
}
public class CeilingFanMediumCommand extends AbstractCeilingFanCommand{

    public CeilingFanMediumCommand(CeilingFan ceilingFan) {
super(ceilingFan);
} protected void doExecute() {
getCeilingFan().medium();
}
}
public class CeilingFanLowCommand extends AbstractCeilingFanCommand {
public CeilingFanLowCommand(CeilingFan ceilingFan) {
super(ceilingFan);
}
protected void doExecute() {
getCeilingFan().low();
}
}
public class CeilingFanOffCommand extends AbstractCeilingFanCommand {

    public CeilingFanOffCommand(CeilingFan ceilingFan) {
super(ceilingFan);
} protected void doExecute() {
getCeilingFan().off();
}
}

最后是测试类:

public class TestClient {

    private static RemoteControl REMOTE_CONTROL = new RemoteControl();
static{ //初始化灯也可以是抽象的,因为,Light的命令,并不关心是什么灯
Light livingRoomLight = new Light("卧室");
//设置开灯,关灯命令
REMOTE_CONTROL.setCommand(0, new LightOnCommand(livingRoomLight), new LightOffCommand(livingRoomLight)); //初始化吊扇
CeilingFan ceilingFan = new CeilingFan("卧室");
//初始化吊扇每个档位的命令
CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
REMOTE_CONTROL.setCommand(1, new CeilingFanLowCommand(ceilingFan), ceilingFanOff);
REMOTE_CONTROL.setCommand(2, new CeilingFanMediumCommand(ceilingFan), ceilingFanOff);
REMOTE_CONTROL.setCommand(3, new CeilingFanHighCommand(ceilingFan), ceilingFanOff);
} public static void main(String[] args) { System.out.println(REMOTE_CONTROL); REMOTE_CONTROL.onButtonWasPushed(0);
REMOTE_CONTROL.offButtonWasPushed(0); System.out.print("撤销命令执行:");
REMOTE_CONTROL.undoButtonWasPushed(); REMOTE_CONTROL.onButtonWasPushed(1);
REMOTE_CONTROL.onButtonWasPushed(2);
System.out.print("撤销命令执行:");
REMOTE_CONTROL.undoButtonWasPushed(); REMOTE_CONTROL.onButtonWasPushed(3);
System.out.print("撤销命令执行:");
REMOTE_CONTROL.undoButtonWasPushed(); REMOTE_CONTROL.offButtonWasPushed(3);
System.out.print("撤销命令执行:");
REMOTE_CONTROL.undoButtonWasPushed();
}
}

测试结果:

梳理一下,命令模式和这里的对应关系:

Invoker:RemoteControl(遥控器)

Command:Command接口

ConcereteCommand:LightOnCommand,LightOffCommand,CeilingFanLowCommand....

Receiver:Light,CeilingFan

Client:TestClient

对TestClient和具体的驱动类(Light,CeilingFan)实现解耦,Client不直接调用驱动类,而是基于遥控器(Invoker)制定的标准,来调用具体的命令实现类。

这样,在新增新的家电驱动的时候,只需要新增命令实现类和Client的初始化代码即可,也可以做成配置文件,这样就不用去改TestClient代码了

四、命令模式优缺点

优点:

1,降低了请求者和执行者之间的耦合性

2,新命令的扩展性很强,需要新的命令,只需要加一个实现类即可,对现有代码影响很小

3,命令之间还可以相互组合形成新的命令,比如在遥控器里面要实现一个闪光效果,可以用LightOnCommand,LightOffCommand,结合起来做一个宏命令

缺点:

命令的数量可能会很多,每个指令都必须封装成一个类,如果指令过多,那么开发的成本会很高

所以,只有在请求者和执行者真的需要解耦的时候才使用:

比如:A->B的调用中新增了很多操作,日志,排队,各种逻辑,需要引入Invoker

比如:有A->C逻辑和B相似,则可以引入Command接口来抽象这一流程

比如:B,C逻辑需要撤销,或者重做的时候,也可以引入命令模式

总之,根据需要来抽象,没有最好的,只有当下最合适的

或许,spring aop就很合适呢?

headfirst设计模式(7)—命令模式的更多相关文章

  1. HeadFirst设计模式之命令模式

    一. 1.因为是操作经常变化,所以封装操作为command对象.You can do that by introducing “command objects” into your design. A ...

  2. 设计模式 ( 十三 ) 命令模式Command(对象行为型)

    设计模式 ( 十三 ) 命令模式Command(对象行为型) 1.概述         在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需 ...

  3. 乐在其中设计模式(C#) - 命令模式(Command Pattern)

    原文:乐在其中设计模式(C#) - 命令模式(Command Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 命令模式(Command Pattern) 作者:webabcd ...

  4. 面向对象设计模式_命令模式(Command)解读

    在.Net框架中很多对象的方法中都会有Invoke方法,这种方法的设计实际是用了设计模式的命令模式, 模式图如下 其核心思路是将Client 向Receiver发送的命令行为进行抽象(ICommand ...

  5. 折腾Java设计模式之命令模式

    博客原文地址 折腾Java设计模式之命令模式 命令模式 wiki上的描述 Encapsulate a request as an object, thereby allowing for the pa ...

  6. 用Java 8 Lambda表达式实现设计模式:命令模式

    在这篇博客里,我将说明如何在使用 Java 8 Lambda表达式 的函数式编程方式 时实现 命令 设计模式 .命令模式的目标是将请求封装成一个对象,从对客户端的不同类型请求,例如队列或日志请求参数化 ...

  7. python设计模式之命令模式

    python设计模式之命令模式 现在多数应用都有撤销操作.虽然难以想象,但在很多年里,任何软件中确实都不存在撤销操作.撤销操作是在1974年引入的,但Fortran和Lisp分别早在1957年和195 ...

  8. Head First 设计模式 --6 命令模式

    命令模式:将"请求"封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象.命令模式也支持可撤销的操作.用到的原则:1.封装变化2.组合优于继承3.针对接口编程,不能针对实现 ...

  9. C#设计模式(15)——命令模式(Command Pattern)

    一.前言 之前一直在忙于工作上的事情,关于设计模式系列一直没更新,最近项目中发现,对于设计模式的了解是必不可少的,当然对于设计模式的应用那更是重要,可以说是否懂得应用设计模式在项目中是衡量一个程序员的 ...

  10. 【GOF23设计模式】命令模式

    来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_命令模式.数据库事务机制底层架构实现.撤销和回复 package com.test.command; public cla ...

随机推荐

  1. 登录测试用例sql语句注入

    利用SQL注入漏洞登录后台的实现方法 作者: 字体:[增加 减小] 类型:转载 时间:2012-01-12我要评论 工作需要,得好好补习下关于WEB安全方面的相关知识,故撰此文,权当总结,别无它意.读 ...

  2. python_形参何时影响实参

    §对于绝大多数情况下,在函数内部直接修改形参的值不会影响实参.例如: >>> def addOne(a): print(a) a += 1 print(a) >>> ...

  3. JS 数据类型、赋值、深拷贝和浅拷贝

    js 数据类型 六种 基本数据类型: Boolean. 布尔值,true 和 false. null. 一个表明 null 值的特殊关键字. JavaScript 是大小写敏感的,因此 null 与 ...

  4. 几张图帮你理解 docker 基本原理及快速入门

    写的非常好的一篇文章,不知道为什么被删除了.  利用Google快照,做个存档. 快照地址:地址 作者地址:青牛 什么是docker Docker 是一个开源项目,诞生于 2013 年初,最初是 do ...

  5. eclipse下的tomcat配置https(最简单得配置https)

    近期公司列出一大堆的东西,其中包括https,啥也不想说,你们是无法理解的苦逼的我的 本文不是双向认证, 双向认证需要让客户端信任自己生成的证书,有点类似登录银行网站的情,如果想知道双向认证的同志可以 ...

  6. thinkphp 自动生成模块目录结构

    要达到的目的 在application目录下创建自定义模块如admin,用命令行方式自动创建该目录及目录下默认结构 要运行的命令 > php think build --module admin ...

  7. 关于TCP/IP,必知必会的十个经典问题[转]

    关于TCP/IP,必知必会的十个问题 原创 2018-01-25 Ruheng 技术特工队   本文整理了一些TCP/IP协议簇中需要必知必会的十大问题,既是面试高频问题,又是程序员必备基础素养. 一 ...

  8. 计算机组装:台式机更换CPU

    前言: 由于想在一台WindowsXP操作系统的台式机上使用虚拟机,但是这个台式机原装的CPU(Intel 奔腾 E2200)不支持虚拟化,所以我找了一颗支持虚拟化的CPU(Intel 酷睿 E850 ...

  9. SwaggerUI--SosoApi

    1.SwaggerUI是什么? Swagger UI是一款RESTFUL接口的文档在线自动生成+功能测试功能软件. Swagger-UI 的官方地址:http://swagger.io/ Github ...

  10. 2101: Bake Off

    Description Davy decided to start a weekend market stall where he sells his famous cakes. For the fi ...