正文

一、定义

命令模式将“请求”封装成对象(命令对象),以便使用不同的“请求”来参数化其他对象。

要点:

  • 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。
  • 被解耦的两者之间通过命令对象进行沟通。命令对象封装了接收者和一个或多个动作。
  • 命令对象提供一个 execute() 方法,该方法封装了接收者的动作。当此方法被调用时,接收者就会执行这些动作。
  • 调用者持有一个或多个命令对象,通过调用命令对象的 execute() 方法发出请求,这会使得接收者的动作被调用。

二、实现步骤

1、创建接收者类

接收者类中包含要执行的动作。

(1)接收者A

/**
* 接收者A
*/
public class ReceiverA { /**
* 动作1
*/
public void action1() {
System.out.println("ReceiverA do action1");
} /**
* 动作2
*/
public void action2() {
System.out.println("ReceiverA do action2");
}
}

(2)接收者B

/**
* 接收者B
*/
public class ReceiverB { /**
* 动作1
*/
public void action1() {
System.out.println("ReceiverB do action1");
} /**
* 动作2
*/
public void action2() {
System.out.println("ReceiverB do action2");
}
}

2、创建命令接口

/**
* 命令接口
*/
public interface Command { /**
* 执行命令
*/
public void execute();
}

3、创建具体的命令,并实现命令接口

命令对象中,封装了命令接收者和相关动作。

(1)命令A1

/**
* 命令A1
*/
public class ConcreteCommandA1 implements Command{ /**
* 接收者A
*/
ReceiverA receive; public ConcreteCommandA1(ReceiverA receive) {
this.receive = receive;
} @Override
public void execute() {
// 接收者A执行动作1
receive.action1();
}
}

(2)命令A2

/**
* 命令A2
*/
public class ConcreteCommandA2 implements Command{ /**
* 接收者A
*/
ReceiverA receive; public ConcreteCommandA2(ReceiverA receive) {
this.receive = receive;
} @Override
public void execute() {
// 接收者A执行动作2
receive.action2();
}
}

(3)命令B1

/**
* 命令B1
*/
public class ConcreteCommandB1 implements Command{ /**
* 接收者B
*/
ReceiverB receive; public ConcreteCommandB1(ReceiverB receive) {
this.receive = receive;
} @Override
public void execute() {
// 接收者B执行动作1
receive.action1();
}
}

(4)命令B2

/**
* 命令B2
*/
public class ConcreteCommandB2 implements Command{ /**
* 接收者B
*/
ReceiverB receive; public ConcreteCommandB2(ReceiverB receive) {
this.receive = receive;
} @Override
public void execute() {
// 接收者B执行动作2
receive.action2();
}
}

4、创建调用者

调用者通过持有的命令对象,来调用接收者的动作。

/**
* 调用者
*/
public class Invoker { /**
* 命令对象
*/
Command command; /**
* 设置命令
*/
public void setCommand(Command command) {
this.command = command;
} /**
* 调用动作
*/
public void invoke() {
command.execute();
}
}

5、调用者执行命令

public class Test {

    public static void main(String[] args) {
// 命令调用者
Invoker invoker = new Invoker(); // 命令接收者
ReceiverA receiverA = new ReceiverA();
ReceiverB receiverB = new ReceiverB(); // 创建命令,并指定接收者
Command commandA1 = new ConcreteCommandA1(receiverA);
Command commandA2 = new ConcreteCommandA2(receiverA);
Command commandB1 = new ConcreteCommandB1(receiverB);
Command commandB2 = new ConcreteCommandB2(receiverB); // 调用者执行命令
invoker.setCommand(commandA1);
invoker.invoke();
invoker.setCommand(commandA2);
invoker.invoke();
invoker.setCommand(commandB1);
invoker.invoke();
invoker.setCommand(commandB2);
invoker.invoke();
}
}

三、举个栗子

1、背景

假设你要设计一个家电自动化遥控器的 API。这个遥控器具有七个可编程的插槽(每个都可以指定到一个不同的家电装置),每个插槽都有对应的“开”、“关”按钮。

你希望每个插槽都能够控制一个或一组装置。并且,这个遥控器适用于目前的装置和任何未来可能出现的装置。

2、实现

(1)创建家电类

/**
* 电灯(接收者)
*/
public class Light { /**
* 电灯位置
*/
String location; public Light(String location) {
this.location = location;
} /**
* 开灯
*/
public void on() {
System.out.println(location + " light is on");
} /**
* 关灯
*/
public void off() {
System.out.println(location + " light is off");
}
}
/**
* 音响(接收者)
*/
public class Stereo { /**
* 打开音响
*/
public void on() {
System.out.println("Stereo is on");
} /**
* 关闭音响
*/
public void off() {
System.out.println("Stereo is off");
} /**
* 设置为播放CD
*/
public void setCd() {
System.out.println("Stereo is set for CD input");
} /**
* 设置为播放DVD
*/
public void setDvd() {
System.out.println("Stereo is set for DVD input");
} /**
* 设置音量
*/
public void setVolume(int volume) {
System.out.println("Stereo volume set to " + volume);
}
}

(2)创建命令接口

/**
* 命令接口
*/
public interface Command { /**
* 执行命令
*/
public void execute();
}

(3)创建具体的命令,并继承命令接口

/**
* 无命令
*/
public class NoCommand implements Command { @Override
public void execute() {}
}
/**
* 开灯命令
*/
public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light) {
this.light = light;
} @Override
public void execute() {
light.on();
}
}
/**
* 关灯命令
*/
public class LightOffCommand implements Command { Light light; public LightOffCommand(Light light) {
this.light = light;
} @Override
public void execute() {
light.off();
}
}
/**
* 打开音响播放CD命令
*/
public class StereoOnWithCDCommand implements Command { Stereo stereo; public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
} @Override
public void execute() {
stereo.on();
stereo.setCd();
stereo.setVolume(11);
}
}
/**
* 关闭音响命令
*/
public class StereoOffCommand implements Command { Stereo stereo; public StereoOffCommand(Stereo stereo) {
this.stereo = stereo;
} @Override
public void execute() {
stereo.off();
}
}

(4)创建遥控器类

/**
* 遥控器(调用者)
*/
public class RemoteControl { Command[] onCommands;
Command[] offCommands; public RemoteControl() {
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;
}
} /**
* 设置插槽命令
*/
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
} /**
* “开”按钮被按下
*/
public void onButtonWasPushed(int slot) {
// 执行开命令
onCommands[slot].execute();
} /**
* “关”按钮被按下
*/
public void offButtonWasPushed(int slot) {
// 执行关命令
offCommands[slot].execute();
}
}

(5)使用遥控器控制家电

public class Test {

    public static void main(String[] args) {
// 遥控器
RemoteControl remoteControl = new RemoteControl(); // 家电
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
Stereo stereo = new Stereo(); // 创建命令,并指定家电
Command livingRoomLightOn = new LightOnCommand(livingRoomLight);
Command livingRoomLightOff = new LightOffCommand(livingRoomLight);
Command kitchenLightOn = new LightOnCommand(kitchenLight);
Command kitchenLightOff = new LightOffCommand(kitchenLight);
Command stereoOnWithCD = new StereoOnWithCDCommand(stereo);
Command stereoOff = new StereoOffCommand(stereo); // 设置插槽命令
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, stereoOnWithCD, stereoOff); // 遥控器执行命令
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
}
}

四、命令模式的更多用途

1、队列请求

命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。

因此,命令模式可以用来实现队列请求。具体做法是:在工作队列的一端添加命令,另一端通过线程取出命令,调用它的 execute() 方法,等调用完成后,将此命令对象丢弃,再取出下一个命令……

2、日志请求

某些应用需要我们将所有的动作都记录在日志中,并能在系统死机后,重新调用这些动作恢复到之前的状态。

命令模式能够支持这一点。具体做法是:当我们执行命令时,利用序列化将命令对象存储在磁盘中。一旦系统死机,再利用反序列化将命令对象重新加载,并依次调用这些对象的 execute() 方法。

《Head First 设计模式》:命令模式的更多相关文章

  1. linkin大话设计模式--命令模式

    linkin大话设计模式--命令模式 首先考虑一种应用情况,某个方法需要完成某一个功能,这个功能的大部分功能已经确定了,但是有可能少量的步骤没法确定,必须等到执行这个方法才可以确定. 也就是说,我们写 ...

  2. 【设计模式】Java设计模式 - 命令模式

    Java设计模式 - 命令模式 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目录 Ja ...

  3. [Head First设计模式]餐馆中的设计模式——命令模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  4. JAVA 设计模式 命令模式

    用途 命令模式 (Command) 将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化:对请求排队或请求日志,以及支持可撤销的操作. 命令模式是一种行为型模式. 结构

  5. 深入浅出设计模式——命令模式(Command Pattern)

    模式动机 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请 ...

  6. Java设计模式-命令模式(Command)

    命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行.这个过程好在,三者相互解耦,任何一方都不用去依赖其 ...

  7. 设计模式--命令模式(Command)

    基本概念:  Command模式也叫命令模式 ,是行为设计模式的一种.Command模式通过被称为Command的类封装了对目标对象的调用行为以及调用参数,命令模式将方法调用给封装起来了. 命令模式的 ...

  8. javascript设计模式——命令模式

    前面的话 假设有一个快餐店,而我是该餐厅的点餐服务员,那么我一天的工作应该是这样的:当某位客人点餐或者打来订餐电话后,我会把他的需求都写在清单上,然后交给厨房,客人不用关心是哪些厨师帮他炒菜.餐厅还可 ...

  9. C++设计模式——命令模式

    什么是命令模式? 在GOF的<设计模式:可复用面向对象软件的基础>一书中对命令模式是这样说的:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以 ...

  10. 浅谈js设计模式 — 命令模式

    命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦 ...

随机推荐

  1. [译]使用DOT语言和GraphvizOnline来可视化你的ASP.NETCore3.0终结点01

    这是系列文章中的第一篇:使用GraphvizOnline可视化ASP.NETCore3.0终结点.. 第1部分-使用DOT语言来可视化你的ASP.NETCore3.0终结点(本文) 第2部分-向ASP ...

  2. 软件测试工程师入门——Linux【使用说明书】

    先来说一下linux是什么? linux 是一个开源.免费的操作系统,其稳定性.安全性.处理多并发已经得到业界的认可,目前很多中性,大型甚至是巨型项目都在使用linux. linux 内核:redha ...

  3. Ubuntu18.04安装Docker并部署(编译、发布、构建镜像)Asp.NetCore项目全过程笔记

      环境准备:阿里云Ubuntu18.04 全新安装   一.安装Docker 1.删除旧版本并更新包索引: sudo apt-get remove docker docker-engine dock ...

  4. JVM——内存区域:运行时数据区域详解

    关注微信公众号:CodingTechWork,一起学习进步. 引言   我们经常会被问到一个问题是Java和C++有何区别?我们除了能回答一个是面向对象.一个是面向过程编程以外,我们还会从底层内存管理 ...

  5. 详解TCP一:三次握手、四次挥手

    TCP协议同样是运输层的协议,掌握TCP重点要关注这几个问题:顺序问题.丢包问题.连接维护.流量控制.拥塞控制.先解析下TCP报文段结构,相比于UDP要复杂很多. 首先还是两个端口号,对应着具体的应用 ...

  6. 【mysql】- 锁篇(上)

    回顾 问题 事务并发执行时可能带来各种问题,并发事务访问相同记录的情况大致可以划分为3种 读-读情况:即并发事务相继读取相同的记录 读取操作本身不会对记录有什么影响,并不会引起什么问题,所以允许这种情 ...

  7. POJ2774 --后缀树解法

    POJ2774 Long Long Message --后缀树解法 原题链接 题意明确说明求两字符串的最长连续公共子串,可用字符串hash或者后缀数据结构来做 关于后缀树 后缀树的原理较为简单,但 \ ...

  8. jmeter零散知识点

  9. PowerJob 技术综述,能领悟多少就看你下多少功夫了~

    本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列.从本章开始,就正式进入 PowerJob 框架的技术 ...

  10. Java基础之(IO流)

    简介: 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作. 一.File ...