一、命令模式定义

命令大家都不会陌生,那么在开始命令模式之前,可以想象一下生活中的命令模式的特点:

如老板命令你完成一个OA项目是一个命令,接着看看其特点:

1、在上面的命令中,命令的执行者肯定是聪明的你了。具体的执行方法,可能是通过vs实现,或者是通过eclipse实现,由此看来:命令要有个命令的执行者,还要有个命令的执行方法。

2、命令的发出者很明显是老板,老板还有个发出方法,可能是通过电话给你说,也可能给你邮件给你说,也可能是通过开会给你说。所以命令的发出者要有一个命令,还要有个发出的方法。

3、最后看看命令,命令有个名字,命令的肯定要执行。而且命令是在boss给你发出通知后执行的。

接下来看看命令模式的定义:

命令模式:将请求封装成对象,以便使用不同的请求、日志、队列等来参数化其他对象。命令模式也支持撤销操作。

每次讲一个模式时,从定义都不能体会其中的技巧,所以接着我会通过举例子来说明命令模式。

二、命令模式的举例

下面来看看多用遥控器是如何使用命令模式的。

2.1需求

假设某个公司需要设计一个多用功能的遥控器。基本的需求如下:

该遥控器有可以控制风扇,白炽灯,热水器等等的多对开关,而且可能还有其他的电器,暂时不做其功能,但是希望可以保留接口,用的时间可以方便的扩展。

除上面的需求之外,还需要有个按钮,可以撤销上一步的操作。基本功能如下图:

2.2问题

在设计遥控器时,风扇,白炽灯,热水器的开关方法已经定义好,其名字各不相同。不妨设置其方法为如下:

由于各种电器的开关方法都不一样,而且还存在一个待扩展的电器,如果没有学习命名模式之前,我们在设置扩展的开关时,会出现的问题是什么呢?假设现在有电视,冰箱还可能会用到遥控器,那么我们会在最后一个开关上写if else,当然如果哪一天有多了一个大门也加入了我们的遥控的行列,这样我们继续加if else ,很显然随着电器的高速发展,会有多个需要遥控可以控制的。

举个例子,如果我们是需要遥控的客户,现在有一款遥控如果有遥控可以进行扩展,一种是可以扩展指定类型的,像上面的,只能再去扩展电视和冰箱中的一种,偶尔有一天你看到隔壁邻居的门,也可以使用遥控了,所以你去把你的高级遥控器,拿到扩展店时,扩展工程师说了,现在只能扩展电视和冰箱,不支持对大门的遥控扩展.

我们肯定是希望,可以自由的扩展,大门可以使用遥控了,就对大门扩展,车门使用遥控了,就对车门扩展……其实也就是一种松耦合的实现。

2.3分析问题

为了实现松耦合,我们现在来想一下,周末去请朋友吃饭,服务员mm问你吃什么,你说水煮活鱼,然后在菜单上面,写上水煮活鱼。下个星期天想吃花生米啤酒,同样也写在订单上,然后服务员mm把订单拿给厨师。

在上面的例子中,无论你点了什么菜,服务员mm,只需要知道顾客点的什么菜,从而给不同的厨师做(虽然不是直接的,但最终凉菜会给凉菜的师傅做,热菜的会给热菜的厨师做),然而具体的怎么做是厨师的事情,服务员知道顾客点上面菜,只是为了能正确的交个厨师去做。

其实在这个例子中,也是一个命令模式的例子,不同的订单对应的有不同的厨师,最终订单拿到厨师面前,就是对厨师下了个命令,要做菜了。每订单或者说是每种菜都对应着自己的厨师,所以,客服需要有订单的的引用,订单为了知道调用哪个厨师,需要有厨师引用;两个引用都是使用的组合。从而可以调用多种自己需要对象的方法来实现松耦合。这里记住:客服mm知道顾客点菜的目的是为了让不同的厨师去做。再直接一些就是为了使用不同的命令。

上面的吃饭问题和我们的遥控器问题差不多,都是包含下命令,命令的执行者,以及命令的具体内容。如果还是有些不清楚的话,就用简单的程序模拟一下上面的过程:

 class Program
{
static void Main(string[] args)
{
MM mm = new MM();
//想吃热菜
mm.SetOrder(new ReCaiOrder());
//mm还需要把菜单拿到厨师那里哦
mm.OnOrder();
//想吃凉菜
mm.SetOrder(new LiangCaiOrder());
mm.OnOrder();
Console.ReadKey();
}
} /// <summary>
/// 订单
/// </summary>
interface IOrder
{
void Excute();
} /// <summary>
/// 凉菜做法
/// </summary>
class LiangCaiChuShi
{
public void MakeCook()
{
Console.WriteLine("凉菜~!!!");
}
}
/// <summary>
/// 凉菜订单
/// </summary>
class LiangCaiOrder:IOrder
{
LiangCaiChuShi chushi=new LiangCaiChuShi();
public void Excute()
{
chushi.MakeCook();
}
}
/// <summary>
/// 热菜做法
/// </summary>
class ReCaiChuShi
{
public void Cook()
{
Console.WriteLine("热菜!!");
}
} /// <summary>
/// 热菜订单
/// </summary>
class ReCaiOrder : IOrder
{
ReCaiChuShi chushi=new ReCaiChuShi();
public void Excute()
{
chushi.Cook();
}
}
class MM
{
IOrder order;
public void SetOrder(IOrder order)
{
this.order = order;
}
public void OnOrder()
{
order.Excute();
}
}

上面的例子中,厨师的做法有可能不同,就像我们遥控器的开关一样,电器的开关方法不一定相同。如果要执行,只需要把这个方法包在命令的激发方法中,然后去调用具体的方法就可以了。

尽管上面的例子有些牵强,但是还是模拟了命令的过程,为我们遥控器的设计提供了思路。

2.4解决问题

回到我们的遥控器问题上面来,我们可以先定义好我们的风扇,白炽灯,热水器。然后定义其分别的开关命令,每个命令都有自己对应的电器引用,而且会在命令的Excute中包装电器的开或者关,最后需要把命令安装到遥控器上面,在遥控器上每个按钮都对应有自己的激发方法,其代码如下:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace RemoteControl
{
class Program
{
static void Main(string[] args)
{
//家中的电器
Fan fan=new Fan();
Light light=new Light();
Heater heater=new Heater(); //电器分别对应的命令
FanOffCommand fanOffCommand=new FanOffCommand(fan);
FanOnCommand fanOnCommand=new FanOnCommand(fan);
LightOnCommand lightOnCommand=new LightOnCommand(light);
LightOffCommand lightOffCommand=new LightOffCommand(light);
HeaterOnCommand heaterOnCommand=new HeaterOnCommand(heater);
HeaterOffCommand heaterOffCommand=new HeaterOffCommand(heater);
RemoteControl remoteControl = new RemoteControl(); //设置遥控器
remoteControl.SetCommand(, fanOnCommand, fanOffCommand);
remoteControl.SetCommand(, lightOnCommand, lightOffCommand);
remoteControl.SetCommand(, heaterOnCommand, heaterOffCommand);
//分别测试遥控器的命令
remoteControl.OnButtonWasPress();
remoteControl.OffButtonWasPress();
remoteControl.OnButtonWasPress();
remoteControl.OffButtonWasPress();
Console.ReadKey();
}
} /// <summary>
/// 风扇类
/// </summary>
public class Fan
{
public void FanOn()
{
Console.WriteLine("风扇开了");
}
public void FanOff()
{
Console.WriteLine("风扇关了");
}
}
/// <summary>
/// 灯类
/// </summary>
public class Light
{
public void LightOn()
{
Console.WriteLine("灯亮了");
}
public void LightOff()
{
Console.WriteLine("灯灭了");
}
}
/// <summary>
/// 热水器类
/// </summary>
public class Heater
{
public void HeaterOn()
{
Console.WriteLine("加热中");
}
public void HeaterOff()
{
Console.WriteLine("停止加热");
}
} /// <summary>
/// 命令接口
/// </summary>
public interface ICommand
{
void Excute();
} public class FanOnCommand : ICommand
{
Fan fan;
public FanOnCommand(Fan fan)
{
this.fan = fan;
}
public void Excute()
{
this.fan.FanOn();
}
} public class FanOffCommand : ICommand
{
Fan fan;
public FanOffCommand(Fan fan)
{
this.fan = fan;
}
public void Excute()
{
this.fan.FanOff();
}
} public class LightOnCommand : ICommand
{
Light light;
public LightOnCommand(Light light)
{
this.light = light;
}
public void Excute()
{
light.LightOn();
} } public class LightOffCommand : ICommand
{
Light light;
public LightOffCommand(Light light)
{
this.light = light;
}
public void Excute()
{
this.light.LightOff();
}
} public class HeaterOnCommand : ICommand
{
Heater heater;
public HeaterOnCommand(Heater heater)
{
this.heater = heater;
}
public void Excute()
{
this.heater.HeaterOn();
}
} public class HeaterOffCommand : ICommand
{
Heater heater;
public HeaterOffCommand(Heater heater)
{
this.heater = heater;
}
public void Excute()
{
this.heater.HeaterOff();
}
} public class NoCommand : ICommand
{
public void Excute()
{ }
} public class RemoteControl
{
private ICommand[] onCommands;
private ICommand[] offCommands;
public RemoteControl()
{
ICommand noCommand=new NoCommand();
onCommands = new ICommand[];
offCommands = new ICommand[];
for (int i = ; i < ; i++)
{
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
} public void SetCommand(int slot, ICommand onCommand, ICommand offCommand)
{
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void OnButtonWasPress(int slot)
{
onCommands[slot].Excute();
}
public void OffButtonWasPress(int slot)
{
offCommands[slot].Excute();
} }
}

这样基本上就实现了我们的现有的三种电器的遥控。需要注意的是,在开始初始化遥控器时,对每个命令初始化成了NoCommand,也就是什么都不执行。在命令的初始化经常使用,同时这也解决了我们的在扩展前什么都不做的难题。看了风扇,白炽灯,热水器的遥控实现,进一步的扩展任何的电器,相信都不是什么难事。但是还有个功能没有实现,就是撤销到上一步的操作,接下来我们就来实现撤销操作。

2.5问题的补充说明

撤销操作就想我们遥控中的返回一样。基本上就是灯亮着,突然按了一下关灯,然后再按一下返回键,灯就亮了。其他的电器同样的道理。下面先看一下灯的撤销原理,命令除了执行外还有一个撤销,所以我们需要先都命令的接口添加一个方法。

/// <summary>
/// 命令接口

/// </summary>

public interface ICommand

{

void Excute();

void Undo();

}

对于开灯需要做的修改如下:

public class LightOnCommand : ICommand

   {

Light light;

public LightOnCommand(Light light)

{

this.light = light;

}

public void Excute()

{

light.LightOn();

}

/// <summary>

/// 调用命令的反命令

/// </summary>

public void Undo()

{

light.LightOff();

}

}

其他命令同理,代码会在源码中一并给出。也就是每个命令都有自己的反命令,在Undo方法里面也就是调用反命令的Excute方法。每当按下一个按钮时,就去记录其命令的名称,如果按撤销的话,就执行命名的Undo方法。下面给出主要代码:

public void OnButtonWasPressed(int slot)

   {

onCommands[slot].Excute();

backCommand=onCommands[slot];

}

public void OffButtonWasPressed(int slot)

{

offCommands[slot].Excute();

backCommand = offCommands[slot];

}

public void BackButtonWasPressed()

{

backCommand.Undo();

}

以上是对遥控器对命令的撤销,需要注意两点1、通过记住命令执行之前的状态,然后去恢复到原来的状态。2、在每次执行之后要记住执行的那个命令。也即记住命令和记住状态。

除了一次执行一个命令和撤销一个命令,当然还可以一次执行多个命令。下面给出主要代码:

public class MutlipleCommand : ICommand

   {

ICommand[] commands;

ICommand[] backCommands;

public MutlipleCommand(ICommand[] commands)

{

this.commands = commands;

backCommands = new ICommand[commands.Length];

}

public void Excute()

{

for (int i = 0; i < commands.Length; i++)

{

commands[i].Excute();

backCommands[i] = commands[i];

}

}

public void Undo()

       {

for (int i = 0; i < commands.Length; i++)

{

backCommands[i].Undo();

}

}

}

三、命令模式类图

四、总结

命令模式主要通过中介Command实现了发出命令者和命令的执行者,也即Invoke类和Receiver的松耦合。本文先给出了命令模式的定义,通过吃饭的例子给出了使用命令模式实现遥控器设计思路,最后还提到了撤销命令和一个命令实现多个命令的做法。

源码

命令模式(head first 设计模式5)的更多相关文章

  1. java 之 命令模式(大话设计模式)

    命令模式,笔者一直以为当我们开发的过程中基本上很难用到,直到维护阶段或者重构阶段,我们会发现有些撤销命令和追加命令比较频繁时,自然而然就用到命令模式. 先看下类图 大话设计模式-类图 简单说下类图,最 ...

  2. 设计模式---行为变化模式之命令模式(Command)

    前提:行为变化模式 在组件的构建过程中,组建行为的变化经常导致组件本身剧烈的变化.“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件的变化,实现两者之间的松耦合. 类中非虚函数和静态函数方法 ...

  3. 设计模式C#实现(十五)——命令模式

    意图 0 适用性 1 结构 2 实现 3 效果 4 参考 5 意图 将请求封装成一个对象,客户接受请求参数:可以对请求排队或者记录请求日志,以及可以支持撤销操作 适用性 抽象出待执行的动作以参数化某对 ...

  4. [Head First设计模式]餐馆中的设计模式——命令模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  5. JAVA 设计模式 命令模式

    用途 命令模式 (Command) 将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化:对请求排队或请求日志,以及支持可撤销的操作. 命令模式是一种行为型模式. 结构

  6. 设计模式学习之命令模式(Command,行为型模式)(12)

    一.命令模式的定义 命令模式属于对象的行为型模式.命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开.命令模式的实现可以提供命令的撤销和恢复功能 ...

  7. 深入浅出设计模式——命令模式(Command Pattern)

    模式动机 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请 ...

  8. Head First 设计模式 --6 命令模式

    命令模式:将"请求"封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象.命令模式也支持可撤销的操作.用到的原则:1.封装变化2.组合优于继承3.针对接口编程,不能针对实现 ...

  9. C#设计模式(15)——命令模式(Command Pattern)

    一.前言 之前一直在忙于工作上的事情,关于设计模式系列一直没更新,最近项目中发现,对于设计模式的了解是必不可少的,当然对于设计模式的应用那更是重要,可以说是否懂得应用设计模式在项目中是衡量一个程序员的 ...

  10. 【GOF23设计模式】命令模式

    来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_命令模式.数据库事务机制底层架构实现.撤销和回复 package com.test.command; public cla ...

随机推荐

  1. 前端 HTML的规范

    1.编写HTML规范 1)所有标记元素都要正确的嵌套,不能交叉嵌套.正确写法举例:<h1><font></font></h1> (2)HTML标签通常是 ...

  2. 腾讯在线文档发布:实现QQ、微信多平台多人协作编辑

    18日,腾讯宣布推出专注多人协作的在线文档产品—腾讯文档,据介绍,腾讯文档是一款支持随时随地创建.编辑的多人协作式在线文档工具,拥有一键翻译.实时股票函数和浏览权限安全可控等功能,以及打通QQ.微信等 ...

  3. MySQL数据库运维课程

    MySQL数据库运维课程 http://www.dataguru.cn/article-4834-1.html?union_site=comm100 课程大纲 第一课:机器选型.系统规划 第二课:安装 ...

  4. js-jquery-插件开发(一)

    jQuery插件开发模式 jQuery插件开发方式主要有三种:1.通过$.extend()来扩展jQuery 主要是在jQuery命名空间或者理解成jQuery身上添加了一个静态方法2.通过$.fn ...

  5. (转)Elasticsearch聚合初探——metric篇

    前言 ES中的聚合被分为两大类:Metric度量和bucket桶(原谅我英语差,找不到合适的词语.....就用单词来说吧!).说的通俗点,metric很像SQL中的avg.max.min等方法,而bu ...

  6. [redis] 介绍安装

    redis相关网站 官方网站:http://redis.io/ redis简介 官方介绍:http://redis.io/topics/introduction 百度百科:http://baike.b ...

  7. .NET 互联网技术简介

    概述 技术更新太快,尤其是在互联网公司里,很多新的主流技术,我们还是必须要知道和熟练使用的.下面就给大家简单介绍,入门还是需要大家更努力的去深入学习. 目录 Git 入门 常用软件安装及VS插件工具 ...

  8. http 同步异步请求

    在用户交互模式下,当你改变表单中某个组件的值时, 譬如你填写名字.修改性别.选择爱好的时候,浏览器和服 务器至今没有发生任何交互,只有当你点击submit的时候, 浏览器才会把你的参数,也就是form ...

  9. Django初级手册2-管理界面的使用及定制

    管理界面的使用 管理界面的URL,帐号和密码在第一次输入syncdb时建立 http://127.0.0.1:8000/admin/ 将app加入管理界面 编辑polls/admin.py from ...

  10. 十图详解tensorflow数据读取机制

    在学习tensorflow的过程中,有很多小伙伴反映读取数据这一块很难理解.确实这一块官方的教程比较简略,网上也找不到什么合适的学习材料.今天这篇文章就以图片的形式,用最简单的语言,为大家详细解释一下 ...