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设计模式 — 命令模式
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦 ...
随机推荐
- 实用---生命游戏 Java
本程序由四个类组成: 其中Init_data,用于初始化各个活细胞的状态judge_state,用于判断下一代的细胞状态,并进行更新.set_color,用于给GUI界面中各个细胞涂色set_fram ...
- 关于到美国学习cs的亲身感受,希望对你们有所帮助
1.能否向各位寄托天下的朋友们简单介绍一下你自己?比如你国内的学校(或者什么档次),哪年申请出国的,什么专业,硕士还是博士,在美国的学校(或者什么档次),以及留学经历(毕业时间),现在状态(学生?博后 ...
- (五)Unity插件生成
1)新建空的AndroidStudio工程,但是新建过程时最小SDK版本要与unity一致,如下图所示,本次操作均为api16 2)创建Library,如下图所示,新建module,然后选择Andro ...
- VuforiaAR 教程
Vuforia AR不仅可以识别图片也可以识别三维物体 直接上步骤: 1)开发环境 unity2017直接集成了Vuforia AR开发环境,不过在安装时要选择此项,也可以后续去unity官网下载相 ...
- Cannot read property 'forEach' of undefined
在singer-detail组件中,有一个_normalizeSongs()方法,遍历数组 _normalizeSongs(list) { let ret = []; list.forEach(ite ...
- Xbim.GLTF源码解析(二):IFC和GLTF的对应关系
原创作者:flowell,转载请标明出处:https://www.cnblogs.com/flowell/p/10839179.html IFC IFC是建筑信息模型(BIM)数据开放的国际标准,在建 ...
- 2018.8.3 python中的set集合及深浅拷贝
一.字符串和列表的相互转化 之前写到想把xx类型的数据转化成yy类型的数据,直接yy(xx)就可以了,但是字符串和列表的转化比较特殊,相互之间的转化要通过join()和split()来实现. 例如: ...
- Just For Test
Just For Test
- windows 360浏览器打开网站白屏
1.场景 使用windows的360浏览器打开网页白屏 使用mac 谷歌,360,火狐浏览器打开均正常 2.原因 windows浏览器默认使用的是ie浏览器内核渲染的,js执行时发生错误 3.添加he ...
- linux 查看磁盘信息
一.查看磁盘和分区 ACCB947E:Home zhangsan$ df -h Filesystem Size Used Avail Capacity iused ifree %iused Mount ...