Head First设计模式——命令模式
前言:命令模式我们平常可能会经常使用,如果我们不了解命令模式的结构和定义那么在使用的时候也不会将它对号入座。
举个例子:在winform开发的时候我们常常要用同一个界面来进行文件的下载,但是并不是所有地方都用同一个下载逻辑处理文件,然后下载界面却可以是同一个界面。
为了以后复用下载界面(下载显示,进度条等)我们常常将下载执行操作定义成一个接口,在具体使用的时候实现接口,将具体执行对象设置到下载界面。当下载按钮被按下的时候,就调用设置的具体执行对象(接收者)来执行下载的处理。
那接下来我们就看下命令模式的具体细节和实现,再回头想想我们平时什么时候不经意就使用到了命令模式,这样以后交流使用专业的术语不仅能装还能用。
1、遥控器应用场景
HeadFirst设计模式一书中以遥控器为例实现命令模式,以餐馆点餐讲解命令模式的对象和结构。为了逻辑清晰我们不混合两种讲解方式,只以遥控器为例讲解。
现在需求是有一个遥控器,遥控器上面有控制各种电器的开关,而开关的执行控制电器是由各个厂家开发的设备(对象)插入到对应开关位置的卡槽里面,基于这些条件我们来实现遥控器系统。
简单粗暴的解决方案可以对开关做一个标识,当某个开关被按下时根据开关类型进行if判断。形如 if slot1==Light ,then light.on(), else if slot1==Tv then tv.on() 这种代码将出现一堆,对于以后增加减少开关或者更换开关都是比较糟糕的。而对于设计遥控器类来说我们应该让遥控器代码尽量保持简单,而不用去关心具体厂商类怎么执行。所以我们应该将执行封装在一个命令对象里中,那么我们就试着一步步实现遥控器。
首先我们为命令对象定义一个统一的接。
接口只有一个简单的execute执行命令方法。
public interface Command
{
//执行命令的方法
public void execute();
}
接下来我们实现一个打开电灯的命令
public class Light
{
public void on() {
Console.WriteLine("打开电灯");
} public void off()
{
Console.WriteLine("关闭电灯");
}
} public class LightOnCommand : Command
{
Light light; public LightOnCommand(Light light)
{
this.light = light;
}
public void execute()
{
light.on();
}
}
为了简单我们假设遥控器只有一个开关,实现遥控器。
public class SimpleRemoteControl
{
//卡槽
Command slot; public void setCommand(Command command)
{
slot = command;
} //按下开关
public void ButtonWasPressed() {
slot.execute();
} }
测试
static void Main(string[] args)
{
SimpleRemoteControl remoteControl = new SimpleRemoteControl();
//厂商提供的电灯类,命令的接收者
Light light = new Light(); //我们封装的命令对象,设置接收者
LightOnCommand lightOnCommand = new LightOnCommand(light); //设置遥控器开关对应的命令对象
remoteControl.setCommand(lightOnCommand);
remoteControl.ButtonWasPressed();
Console.ReadKey();
}

2、命令模式、类图
通过上面的例子我们已经使用了命令模式来实现一个简单的遥控器,再回顾【前言】我们说的界面下载文件按钮操作是不是就是一个典型的可以使用命令模式的应用场景。
只是有一点我们可能不会有什么其他厂商设计好的执行类,我们也许直接就在继承接口的命令对象中实现execute的逻辑,而不用再调用其他接收者执行。
这就是“聪明”命令对象,上面我们实现的是“傻瓜”命令对象。这个稍后再说,我们先看命令模式定义和画出类图。
命令模式:将“请求”封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持撤销的操作。

3、完成多开关遥控器和撤销操作
假设遥控器现在有五个开关。我们已经有简单遥控器的经验,那么其他4个开关我们也将对应的命令对象设置上去就行了。定义两个数组用来记录开关对应的命令对象。
public class RemoteControl
{
Command[] onCommands;
Command[] offCommands;
public RemoteControl()
{
onCommands = new Command[5];
offCommands = new Command[5];
Command noCommand = new NoCommand();
for (int i = 0; i < 5; i++)
{
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot,Command commandOn, Command commandOff)
{
onCommands[slot] = commandOn;
offCommands[slot] = commandOff;
} //按下开关
public void OnButtonWasPressed(int slot)
{
onCommands[slot].execute();
}
//关闭开关
public void OffButtonWasPressed(int slot)
{
offCommands[slot].execute();
} //打印出数组命令对象
public override string ToString() {
var sb = new StringBuilder("\n------------Remote Control-----------\n");
for (int i = 0; i < onCommands.Length; i++)
{
sb.Append($"[slot{i}] {onCommands[i].GetType()}\t{offCommands[i].GetType()} \n");
}
return sb.ToString();
} }
在遥控器中我们定义了一个Nocommand类,是为了对遥控器对应的开关初始化命令对象,避免为空报错或者消除开关调用命令对象时检查对象是否为空的判断。
public void OnButtonWasPressed(int slot)
{
if(onCommand[slot]!=null))
onCommands[slot].execute();
}
在许多设计模式中我们都能看到这种初始值或者空对象的使用。甚至有时候,空对象本身也被视为一种设计模式。(感觉这样代码比较优雅O(∩_∩)O)
遥控器完成了,我们还有做一项工作,就是撤销操作。
撤销操作我们同样在命令接口里面定义一个undo 方法。
public interface Command
{
//执行命令的方法
public void execute();
//撤销命令方法
public void undo();
}
然后我们让LightOnCommand实现undo方法,添加LightOffCommand命令对象。
public class LightOnCommand : Command
{
Light light; public LightOnCommand(Light light)
{
this.light = light;
}
public void execute()
{
light.on();
}
public void undo() {
light.off();
}
} class LightOffCommand : Command
{
Light light; public LightOffCommand(Light light)
{
this.light = light;
}
public void execute()
{
light.off();
} public void undo()
{
light.on();
}
}
遥控器里面添加撤销按钮操作UndoButtonWasPressed并用undoCommand属性存储上一次操作。
public class RemoteControl
{
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
public RemoteControl()
{
onCommands = new Command[5];
offCommands = new Command[5];
Command noCommand = new NoCommand();
for (int i = 0; i < 5; i++)
{
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot,Command commandOn, Command commandOff)
{
onCommands[slot] = commandOn;
offCommands[slot] = commandOff;
} //按下开关
public void OnButtonWasPressed(int slot)
{
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
//关闭开关
public void OffButtonWasPressed(int slot)
{
offCommands[slot].execute();
undoCommand = offCommands[slot];
} public void UndoButtonWasPressed() {
undoCommand.undo();
}
//打印出数组命令对象
public override string ToString() {
var sb = new StringBuilder("\n------------Remote Control-----------\n");
for (int i = 0; i < onCommands.Length; i++)
{
sb.Append($"[slot{i}] {onCommands[i].GetType()}\t{offCommands[i].GetType()} \n");
}
return sb.ToString();
} }
测试:

4、补充总结
补充:
①命令模式的接收者不一定要存在,之前提到过“聪明”和“傻瓜”命令对象,如果以“聪明”命令对象设计,调用者和接收者之间解耦程度比不上“傻瓜”命令对象,但是我们在使用比较简单的时候仍然可以使用“聪明”命令对象设计。
②撤销例子我们只做了返回最后一次操作,如果要撤销许多次我们可以对操作记录进行保存到堆栈,不管什么时候撤销,我们都可以从堆栈中取出最上层命令对象执行撤销操作。
命令模式常被用于队列请求,日志请求。当队列按照顺序取到存放的命令对象后调用执行方法就行了而不用去管具体执行什么。
日志请求在某些场合可以用来将所有动作记录在日志中,并能在系统死机后通过日志记录进行恢复到之前的状态(撤销)。对于更高级的的应用而言,这些技巧可以应用到事务(transaction)处理中。
通过简单到更进一步的实现讲解了命令模式和一些灵活点和需要注意的点,有什么理解不到位的欢迎指正。
Head First设计模式——命令模式的更多相关文章
- linkin大话设计模式--命令模式
linkin大话设计模式--命令模式 首先考虑一种应用情况,某个方法需要完成某一个功能,这个功能的大部分功能已经确定了,但是有可能少量的步骤没法确定,必须等到执行这个方法才可以确定. 也就是说,我们写 ...
- 【设计模式】Java设计模式 - 命令模式
Java设计模式 - 命令模式 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目录 Ja ...
- [Head First设计模式]餐馆中的设计模式——命令模式
系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...
- JAVA 设计模式 命令模式
用途 命令模式 (Command) 将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化:对请求排队或请求日志,以及支持可撤销的操作. 命令模式是一种行为型模式. 结构
- 深入浅出设计模式——命令模式(Command Pattern)
模式动机 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请 ...
- Java设计模式-命令模式(Command)
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行.这个过程好在,三者相互解耦,任何一方都不用去依赖其 ...
- 设计模式--命令模式(Command)
基本概念: Command模式也叫命令模式 ,是行为设计模式的一种.Command模式通过被称为Command的类封装了对目标对象的调用行为以及调用参数,命令模式将方法调用给封装起来了. 命令模式的 ...
- javascript设计模式——命令模式
前面的话 假设有一个快餐店,而我是该餐厅的点餐服务员,那么我一天的工作应该是这样的:当某位客人点餐或者打来订餐电话后,我会把他的需求都写在清单上,然后交给厨房,客人不用关心是哪些厨师帮他炒菜.餐厅还可 ...
- C++设计模式——命令模式
什么是命令模式? 在GOF的<设计模式:可复用面向对象软件的基础>一书中对命令模式是这样说的:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以 ...
- 浅谈js设计模式 — 命令模式
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦 ...
随机推荐
- Spring Boot Actuator 整合 Prometheus
简介 Spring Boot 自带监控功能 Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况.Bean加载情况.环境变量.日志信息.线程信息等.这一节结合 Prometheus .G ...
- ThinkPHP架构(一)-TP原理及路径问题及后台实现实例
一直用CSDN的博客,由于域名当时注册写的不合适,现在想来博客园写博客,以后要坚持写啦,记录自己的技术学习路程 本人两个月前,刚完成基于PHP的研会门户网站,虽然实现了基本功能,但感觉技术有些单薄,便 ...
- DeCantor Expansion (逆康托展开)
Background\text{Background}Background The \text{The }The Listen&Say Test will be hold on May 11, ...
- [Luogu2323] [HNOI2006]公路修建问题
题目描述 输入输出格式 输入格式: 在实际评测时,将只会有m-1行公路 输出格式: 输入输出样例 输入样例#1: 复制 4 2 5 1 2 6 5 1 3 3 1 2 3 9 4 2 4 6 1 输出 ...
- 安装配置 Android Studio
概述 Android Studio 本身应该是开箱即用的,但是由于 dl.google.com 访问太慢,导致了这样那样的问题,因此我们只需要改一下 hosts 就行了 具体步骤 在Ping检测网站查 ...
- 是可忍孰不可忍!!nodepad++作者台独分子,恶毒言论!!!
本来用了两年这个软件吧,不带任何情感的,单纯辅助工具.直到今天,在GitHub上,发现了这个作者以及一些同党都是一群尼玛生在中国骂中国的狗币. https://github.com/notepad-p ...
- IDEA配置tomcat报错
昨晚想Eclipse转IDEA,谁知道在tomcat就卡住了,难受.今天一下就解决了,记录一下(没有保存错误信息的截图[/敲打]). 问题描述: 运行的时候tomcat卡在Deployment of ...
- PCES - alpha阶段测试报告
测试计划 测试目的 本测试目的在于测试项目完成情况,以及分析测试结果,为下一轮开发提供解决方案 测试项目 学生用户登录测试 课程信息检索测试 服务器测试 在测试过程中出现的Bug 用户界面间的跳转逻辑 ...
- redis面试题及答案
1. Redis有哪些数据结构? 2. 使用过Redis分布式锁么,它是什么回事? 3. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来? ...
- 【Maven学习笔记】mvn help:system 命令的说明
mvn help:system 命令的说明 笔者用得是windows 10 x64系统 下载了Maven3,正确配置了系统变量M2_HOME的值,并且添加到Path变量路径当中. 简单来说,Maven ...