C#设计模式之11:命令模式
C#设计模式之11:命令模式
命令模式
命令模式用来解决一些复杂业务逻辑的时候会很有用,比如,你的一个方法中到处充斥着if else 这种结构的时候,用命令模式来解决这种问题就会让事情变得简单很多。
命令模式是封装的一个全新的境界:把方法调用封装起来。通过封装方法调用,可以把运算快封装成形,所以调用此运算对象不需要知道事情是如何进行的。通过封装方法调用,可以实现一些很聪明的事,比如日志记录,比如重复使用这些封装来实现撤销(undo)。
命令模式可将“动作的请求者”从“动作的执行者对象中解耦。
定义:命令模式将”请求“封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
下面是命令模式的UML定义:

Command:定义命令的接口,声明执行的方法。
命令对象通过在特定的接收者上绑定一组动作来封装一个请求,要达到这一点,命令对象将动作和接收者抱紧对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟那个接收者进行了那些动作,只知道如果调用execute()方法,请求的目的就能达到。
实现命令模式
背景:假设某个公司需要设计一个多用功能的遥控器。基本的需求如下:
该遥控器有可以控制风扇,白炽灯,热水器等等的多对开关,而且可能还有其他的电器,暂时不做其功能,但是希望可以保留接口,用的时间可以方便的扩展。
除上面的需求之外,还需要有个按钮,可以撤销上一步的操作。基本功能如下图:

设计遥控器
public class RemoteControl
{
private readonly ICommand[] _onCommands;
private readonly ICommand[] _offCommands; public RemoteControl()
{
_onCommands = new ICommand[];//因为遥控器上面只有7个插槽(插槽对应Command)
_offCommands = new ICommand[];//因为遥控器上面只有7个插槽(插槽对应Command)
var noCommand = new NoCommand();
for (int index = ; index < ; index++)
{
_onCommands[index] = noCommand;
}
} public void SetCommand(int index, ICommand onCommand, ICommand offCommand)
{
_onCommands[index] = onCommand;
_offCommands[index] = offCommand;
} public void OnButtonWasPushed(int index)
{
_onCommands[index].Execute();
} public void OffButtonWasPushed(int index)
{
_offCommands[index].Execute();
}
}
设计命令
上面我们初始化RemoteControl的时候用到了一个nocommand,这个命令什么都不做,我们只是将遥控器上面的插槽都初始化成一个空命令,这个空命令格式如下:
public class NoCommand : ICommand
{
public void Execute()
{
Console.WriteLine("No command was executed!");
}
}
NoCommand对象是一个空对象,当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任交给空对象,举例来说,遥控器不可能一出场就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品。当调用它的execute()方法时,这种对象什么事情也不做。
然后,我们做一个开关灯的命令:
public class LightOnCommand : ICommand
{
private readonly Light _light;//这个字段就是ConcreteCommand中的一个Receiver,多用组合少用继承! public LightOnCommand(Light light)
{
_light = light;
} public void Execute()
{
_light.On();
}
}
可以看到LightOnCommand就是一个控制灯开的命令,它对应命令模式中的ConcreteCommand,里面有一个_light字段,这个_light字段对应的是Receiver,真正的动作执行都是由Receiver来决定的。同样,我们可以设计一个关灯的Command:
public class LightOffCommand : ICommand
{
private readonly Light _light; public LightOffCommand(Light light)
{
_light = light;
} public void Execute()
{
_light.Off();
}
}
代码结构基本相同,不在赘述
一个Receiver,我们上面代码中是一个灯,他就是一个类,真正执行动作的就是这个灯,Receiver:
public class Light
{
public void On()
{
Console.WriteLine("light's on");
} public void Off()
{
Console.WriteLine("light's off");
}
}
这样,关键的几个角色就已经设计完毕,我们看一下效果:
class Program
{
static void Main(string[] args)
{
var control = new RemoteControl();
var light = new Light();
var lightOnCommand = new LightOnCommand(light);
var lightOffCommand = new LightOffCommand(light); control.SetCommand(, lightOnCommand, lightOffCommand);
control.OnButtonWasPushed();
Console.ReadKey();
}
}
运行一下,效果良好,能够实现我们的目的。
实现撤销功能
接下来我们需要为命令设计一个撤销按钮,首先我们要给ICommand接口来设计一个Undo动作:
public interface ICommand
{
void Execute();
void Undo();
}
然后让关灯和开灯等实现这个新的接口方法:
public class LightOffCommand : ICommand
{
private readonly Light _light; public LightOffCommand(Light light)
{
_light = light;
} public void Execute()
{
_light.Off();
} public void Undo()
{
_light.On();
}
}
public class LightOnCommand : ICommand
{
private readonly Light _light;//这个字段就是ConcreteCommand中的一个Receiver,多用组合少用继承! public LightOnCommand(Light light)
{
_light = light;
} public void Execute()
{
_light.On();
} public void Undo()
{
_light.Off();
}
}
可以看出修改这个还是非常容易的,Undo就是Execute的反向操作。
在添加了Undo接口方法后,还有一个问题需要解决,那就是我们需要记录当前按下开关的对应的是哪一个Command,因为我们的Undo按钮只有一个。我们开始这个设计:
public class RemoteControl
{
private readonly ICommand[] _onCommands;
private readonly ICommand[] _offCommands;
private ICommand _undoCommand;
public RemoteControl()
{
_onCommands = new ICommand[];//因为遥控器上面只有7个插槽(插槽对应Command)
_offCommands = new ICommand[];//因为遥控器上面只有7个插槽(插槽对应Command)
var noCommand = new NoCommand();
_undoCommand = noCommand;
for (int index = ; index < ; index++)
{
_onCommands[index] = noCommand;
}
} public void SetCommand(int index, ICommand onCommand, ICommand offCommand)
{
_onCommands[index] = onCommand;
_offCommands[index] = offCommand;
} public void OnButtonWasPushed(int index)
{
_onCommands[index].Execute();
_undoCommand = _onCommands[index];
} public void OffButtonWasPushed(int index)
{
_offCommands[index].Execute();
_undoCommand = _offCommands[index];
} public void UndoButtonWasPushed()
{
_undoCommand.Undo();
}
}
首先,我们添加了一个_undoCommand的字段,它表示当前需要执行撤销操作的那个command,然后,我们在每次执行OnButtonWasPushed或OffButtonWasPushed方法后,记录或更新这个command,让它成为一个UndoCommand。最后,添加一个UndoButtonWasPushed方法,来表示执行一个Undo操作,里面就是调用当前_undoCommand对象上面的Undo方法。
锁的思考
当我们完成上面的操作后,实际上还有一个问题在里面,那就是锁的操作。当我们在遥控器上面乱按一通的话,因为_undoCommand这个对象在多个方法上出现,当不同的线程都在执行这些方法时,_undoCommand这个对象的状态是会不断的变化的,如果我们不给这个对象加一个锁,那么操作就不是原子的,当我们执行UndoButtonWasPushed这个方法的时候,由于其他线程上面对于_undoCommand的访问可能没有用结束,那么这个时候我们执行撤销操作,里面的这个Undo对象可能不是最新的,我们来解决这个问题:
public class RemoteControl
{
private readonly ICommand[] _onCommands;
private readonly ICommand[] _offCommands;
private ICommand _undoCommand;
private readonly static object Lock = new object();//使用lock代码块的必备字段 public RemoteControl()
{
_onCommands = new ICommand[];//因为遥控器上面只有7个插槽(插槽对应Command)
_offCommands = new ICommand[];//因为遥控器上面只有7个插槽(插槽对应Command)
var noCommand = new NoCommand();
_undoCommand = noCommand;
for (int index = ; index < ; index++)
{
_onCommands[index] = noCommand;
}
} public void SetCommand(int index, ICommand onCommand, ICommand offCommand)
{
_onCommands[index] = onCommand;
_offCommands[index] = offCommand;
}
public void OnButtonWasPushed(int index)
{
_onCommands[index].Execute();
lock (Lock)//加锁
{
_undoCommand = _onCommands[index];
}
}
public void OffButtonWasPushed(int index)
{
_offCommands[index].Execute();
lock (Lock)//加锁
{
_undoCommand = _offCommands[index];
}
}
public void UndoButtonWasPushed()
{
lock (Lock)//加锁
{
_undoCommand.Undo();
}
}
}
C#设计模式之11:命令模式的更多相关文章
- 设计模式学习之命令模式(Command,行为型模式)(12)
一.命令模式的定义 命令模式属于对象的行为型模式.命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开.命令模式的实现可以提供命令的撤销和恢复功能 ...
- JAVA设计模式之:命令模式
*通常情况下:行为请求者与实现者通常呈现一种高度耦合状态.有时要对行为进行变更处理处理.高度耦合方式就显得不合适. * 将行为请求者与行为实现者解耦,将一组行为抽象为对象.实现二者之间的松耦合. 这就 ...
- Java设计模式学习记录-命令模式
前言 这次要介绍的是命令模式,这也是一种行为型模式.最近反正没有面试机会我就写博客呗,该投的简历都投了.然后就继续看书,其实看书也会给自己带来成就感,原来以前不明白的东西,书上已经给彻底的介绍清楚了, ...
- java设计模式-----23、命令模式
概念: Command模式也叫命令模式 ,是行为设计模式的一种.Command模式通过被称为Command的类封装了对目标对象的调用行为以及调用参数. 命令模式(Command Pattern)是一种 ...
- c#设计模式系列:命令模式(Command Pattern)
引言 命令模式,我感觉"命令"就是任务,执行了命令就完成了一个任务.或者说,命令是任务,我们再从这个名字上并不知道命令的发出者和接受者分别是谁,为什么呢?因为我们并不关心他们是谁, ...
- 大话设计模式Python实现-命令模式
命令模式(Command Pattern):将请求封装成对象,从而使可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤消的操作. 下面是一个命令模式的demo: #!/usr/bi ...
- 《Head first设计模式》之命令模式
命令模式将"请求"封装成对象,以便使用不同的请求.队列或者日志来参数化其他对象.命令模式也支持可撤销的操作. 一个家电公司想邀请你设计一个家电自动化遥控器的API.这个遥控器有7个 ...
- 重学 Java 设计模式:实战命令模式「模拟高档餐厅八大菜系,小二点单厨师烹饪场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 持之以恒的重要性 初学编程往往都很懵,几乎在学习的过程中会遇到 ...
- 《Head First 设计模式》:命令模式
正文 一.定义 命令模式将"请求"封装成对象(命令对象),以便使用不同的"请求"来参数化其他对象. 要点: 命令模式可将"动作的请求者"从& ...
- C#设计模式系列:命令模式(Command)
1.命令模式简介 1.1>.定义 命令模式的目的是解除命令发出者和接收者之间的紧密耦合关系,使二者相对独立,有利于程序的并行开发和代码的维护.命令模式的核心思想是将请求封装为一个对象,将其作为命 ...
随机推荐
- 奇袭 CodeForces 526F Pudding Monsters 题解
考场上没有认真审题,没有看到该题目的特殊之处: 保证每一行和每一列都恰有一只军队,即每一个Xi和每一个Yi都是不一样 的. 于是无论如何也想不到复杂度小于$O(n^3)$的算法, 只好打一个二维前缀和 ...
- 无废话centos+TDengine+Telegraf+Grafana入门
一.安装TDengine:1.从官网https://www.taosdata.com/cn/getting-started/下载RPM包(tdengine-1.6.2.0-3.el7.x86_64.r ...
- quartus仿真提示: Can't launch the ModelSim-Altera software
quartus仿真提示: Can't launch the ModelSim-Altera software 2017年07月13日 17:54:50 小怪_tan 阅读数:3255 路径中的结尾 ...
- react 核心技术点
1.react生命周期 react生命周期分为初始化阶段.运行阶段.销毁阶段. (1) 初始化阶段: componentWillMount:实例挂载之前 Render:渲染组件 componentDi ...
- 【转】CAD 二次开发--属性块 Block和BlockReference
1.属性块的定义 属性块是有构成的实体和附加信息(属性)组成的,属性块中块的定义与简单块中块的定义一样,而属性的定义主要是通过属性的AttributeDefinition类的有关属性和函数来实现的.具 ...
- Centos7下的文件压缩
[root@web01 ~]# yum provides zip 已加载插件:fastestmirror Loading mirror speeds from cached hostfile * ba ...
- SQLServer常用运维SQL整理(转)
转载地址:https://www.cnblogs.com/tianqing/p/11152799.html 今天线上SQLServer数据库的CPU被打爆了,紧急情况下,分析了数据库阻塞.连接分布.最 ...
- springboot 读取配置文件
读取配置文件 在以前的项目中我们主要在 XML 文件中进行框架配置,业务的相关配置会放在属性文件中,然后通过一个属性读取的工具类来读取配置信息. 在 Spring Boot 中我们不再需要使用这种方式 ...
- java html实体转义
public static void main(String[] args) { String str = " -<->-&-"-'" ...
- VSCode安装MathJax插件
曾经我一直照网上的教程安装了半天都没有安装好,直到我找到了一个叫"Markdown+Math"的插件,安装好之后就可以用了.