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

举个例子:在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. 服务器端 SOCKET 编程

    使用 Socket 的程序在使用 Socket 之前必须调用 WSAStartup() 函数, 此函数在应用程序中用来初始化 Windows Socket DLL, 只有此函数调用成功后,应用程序才可 ...

  2. shark恒破解笔记1-壳内寻找注册码

    记录学习shark恒大教程的学习记录 壳内寻找注册码 OD打开 明显有壳 F9先运行程序 2.Ctrl+G输入401000到解码段,如果出现db ** 说明已经解码过,脱离了程序本身的壳 鼠标右键-& ...

  3. 毕业设计过程中的一些学习Android网站

    安卓巴士:http://www.apkbus.com/CSDN:(下载资源)http://www.csdn.net/?ref=toolbar博客园:http://www.cnblogs.com/极客学 ...

  4. PHP 组件注册的例子

    <?php namespace Test; abstract class Plugin { protected $pluginName = null; abstract public funct ...

  5. 图像处理笔记(二十一):halcon在图像处理中的运用

    概要: 分水岭算法做图像分割 二维码识别 稍后将其他几篇笔记全都补充上概要方便查询. 分水岭算法做图像分割 使用距离变换结合分水岭算法实现图像分割,可以用来分割仅通过阈值分割还是有边缘连接在一起的情况 ...

  6. 我遇到的一些Git问题汇编

    问题一: failed to push some refs to git hint: Updates were rejected because the remote contains work th ...

  7. 百万年薪python之路 -- 异常处理

    异常处理 1.错误的分类: 1.语法错误:(这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正) #语法错误示范一 if #语法错误示范二 def test: pass #语法错 ...

  8. 算法随笔-二叉树遍历的N种姿势

    最近在练习用Python刷算法,leetcode上刷了快300题.一开始怀疑自己根本不会写代码,现在觉得会写一点点了,痛苦又充实的刷题历程.对我这种半路出家的人而言,收获真的很大. 今天就从二叉树遍历 ...

  9. OptimalSolution(3)--链表问题(1)简单

    单链表Node节点类 public class Node { public int val; public Node next; public Node(int val) { this.val = v ...

  10. HDFS基本命令与Hadoop MapReduce程序的执行

    一.HDFS基本命令 1.创建目录:-mkdir [jun@master ~]$ hadoop fs -mkdir /test [jun@master ~]$ hadoop fs -mkdir /te ...