前言:命令模式我们平常可能会经常使用,如果我们不了解命令模式的结构和定义那么在使用的时候也不会将它对号入座。

举个例子:在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设计模式——命令模式的更多相关文章

  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. 朋友外包干了5年java,居然不知道dubbo-monitor是怎么用的?

    Dubbo工具--dubbo-monitor监控平台的发布和使用 1)下载 https://github.com/alibaba/dubbo/archive/dubbo-2.5.8.zip 2)编译 ...

  2. CVE-2019-0708(非蓝屏poc)远程桌面代码执行漏洞复现

    玩了几天 刚回成都  玩电脑复现一下~ 内核漏洞原理暂时 没看懂 别问 ,问就是不懂 0x01 复现环境和Exp准备 漏洞影响范围 Windows 7 Windows Server 2008 R2 W ...

  3. procdump64+mimikatz获取win用户hash密码

    1.导出lsass.exe procdump64.exe -accepteula -ma lsass.exe lsass.dmp 2.执行mimikatz mimikatz.exe "sek ...

  4. shark恒破解笔记4-API断点GetPrivateProfileStringA

    这小节是通过断在GetPrivateProfileStringA,然后找到注册码的. 1.运行程序输入假码111111,提示重启.通过这判断这是一个重启来验证的,那么它是如何来验证的呢?观察程序目录下 ...

  5. go-接口-反射

    接口类型总是代表着某一种类型(即所有实现它的类型)的行为. 一个接口类型的声明通常会包含关键字type.类型名称.关键字interface以及由花括号包裹的若干方法声明. type Animal in ...

  6. 利用Arthas定位线上问题实例

    前言 Arthas是一个类似于Btrace的JVM在线调试分析工具,具体可参考我之前写的一篇博客:利用JVM在线调试工具排查线上问题.本文分享笔者刚遇到的一个问题,虽然不复杂,但是很典型. 问题与分析 ...

  7. Unity - HasExitTime用法

    本文详细分析了AnimatorController中动画切换过渡问题,即Translation过渡及hasExitTime的问题.方法为对实际项目中的所有情况进行分类,规划逻辑图,可视化分析解决这些问 ...

  8. python常用算法(6)——贪心算法,欧几里得算法

    1,贪心算法 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的的时在某种意义上的局部最优解. 贪心算法并不保证会得到最优解,但 ...

  9. 微信小程序初级教程

    小程序代码构成 JSON 配置 WXML 模版 WXSS 样式 JS 逻辑交互 JSON 配置 在小程序中,JSON扮演的静态配置的角色. 小程序配置 app.json { "pages&q ...

  10. LeetCode 1: single-number

    Given an array of integers, every element appears twice except for one. Find that single one. soluti ...