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设计模式 — 命令模式
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦 ...
随机推荐
- strcpy()、strncpy()和memcpy()对比
strcpy()函数声明:char *strcpy(char *dest, const char *src)返回参数:指向最终的目标字符串 dest 的指针.注意事项:只能复制char类型的字符数组, ...
- Java项目部署与远程调试两三事
[开启和进行远程调试]参考https://blog.csdn.net/WSYW126/article/details/748536801.tomcat:配置catalina.sh jpda参数,主要是 ...
- AppBoxFuture: 服务模型的在线调试与性能监测
框架内的服务模型(ServiceModel)用于处理各类业务逻辑(如最简单的CRUD操作),在设计时以类似于伪代码的形式存在,发布时后端会通过Roslyn转换并编译为运行时代码.为了方便开发者作者 ...
- go-linux环境搭建
下载 go1..linux-amd64.tar.gz 解压: tar zxvf go1..linux-amd64.tar.gz -C /usr/local 配置环境变量:vim /root/.bas ...
- 5分钟读懂Linux权限管理
权限管理: 本文用于初学者对Linux文件系统权限的快速了解!! 进程安全上下文: 进程对文件的访问权限应用模型: 进程的属主与文件的属主是否相同:如果相同,则应用属主权限: 否 ...
- qt读取文本
直接 代码: // lyy : 2016/8/26 16:40:11 说明:读取文本 bool FileOpeartion:: GetTheTextContent (const QString str ...
- SQL挑战一 : 查找最晚入职员工的所有信息
以上数据库表: CREATE TABLE `employees` ( `emp_no` int(11) NOT NULL, `birth_date` date NOT NULL, `first_nam ...
- SpringCloud配置中心集成Gitlab(十五)
一 开始配置config服务 config-server pom.xml <dependency> <groupId>org.springframework.cloud< ...
- IoTClient开发3 - ModBusTcp协议客户端实现
前言 进过前面两章的介绍,今天开始正式的实战. 进制转换 很多朋友对于进制转换可能是在刚学计算机的时候有接触,后来做高级语言开发可能就慢慢忘记了.我们做工控开发的时候需要经常进行进制转换,这里和大家一 ...
- 有Bug?你的代码神兽选对了吗
传说每一个优秀的程序员都有自己专属的镇码神兽 通过 工具网址 http://www.makepic.net/Tool/Image2ascii.html 将自己喜欢的神兽图片转成文本, 可以选择不同的分 ...