zoukankan      html  css  js  c++  java
  • 设计模式--11、命令模式

    命令模式(Command)

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

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

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

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

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

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

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

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

    二、命令模式的举例

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

    2.1需求

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

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

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

    image

    2.2问题

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

    image

    由于各种电器的开关方法都不一样,而且还存在一个待扩展的电器,如果没有学习命名模式之前,我们在设置扩展的开关时,会出现的问题是什么呢?假设现在有电视,冰箱还可能会用到遥控器,那么我们会在最后一个开关上写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中包装电器的开或者关,最后需要把命令安装到遥控器上面,在遥控器上每个按钮都对应有自己的激发方法,其代码如下:

    package designpatterns;
    
    import java.util.HashMap;
    
    /*
     * 命令模式
     * 假设某个公司需要设计一个多用功能的遥控器。基本的需求如下:
     * 该遥控器有可以控制风扇,白炽灯,热水器等等的多对开关,而且可能还有其他的电器,
     * 暂时不做其功能,但是希望可以保留接口,用的时候可以方便的扩展。
     */
    
    /*
     * 风扇类
     */
    class Fan
    {
    	public void FanOn()
    	{
    		System.out.println("风扇开了");
    	}
    	public void FanOff()
    	{
    		System.out.println("风扇关了");
    	}
    }
    /*
     * 灯类
     */
    class Light
    {
    	public void FanOn()
    	{
    		System.out.println("电灯开了");
    	}
    	public void FanOff()
    	{
    		System.out.println("电灯关了");
    	}
    }
    
    /*
     * 命令接口
     */
    interface ICommand{
    	void Excute();
    	void Undo();
    }
    
    /*
     * 风扇的命令执行体FanOnCommand
     */
    class FanOnCommand implements ICommand {
    	Fan fan;//定义一个电风扇用于具体的执行
    	public FanOnCommand( Fan fan)
    	{
    		this.fan = fan;
    	}
    	public void Excute() {
    		this.fan.FanOn();
    	}
    	public void Undo() {
    		this.fan.FanOff();
    	}
    }
    /*
     * 电灯的命令执行体FanOnCommand
     */
    class LightOnCommand implements ICommand {
    	Light light;//定义一个电灯用于具体的执行
    	public LightOnCommand( Light light)
    	{
    		this.light = light;
    	}
    	public void Excute() {
    		this.light.FanOn();
    	}
    	public void Undo() {
    		this.light.FanOff();
    	}
    }
    
    /*
     * 命令的请求者
     */
    class RemoteControl {
    	HashMap<String, ICommand> CommandList = 
    			new HashMap<String, ICommand>();  
    	public RemoteControl() {
    		
    	}
    	//添加执行体
    	public void AddCommand(String strCommandName, ICommand OneCommand) {
    		CommandList.put(strCommandName, OneCommand);
    	}
    	//删除执行体
    	public void DeleteCommand(String strCommandName) {
    		CommandList.remove(strCommandName);
    	}
    	//执行一个命令
    	public void ExcuteCommand(String strCommandName) {
    		CommandList.get(strCommandName).Excute();
    	}
    	//执行一个撤销命令
    	public void UndoCommand(String strCommandName) {
    		CommandList.get(strCommandName).Undo();
    	}
    }
    public class Command {
    
    	public static void main(String[] args) {
    		Fan fan = new Fan();
    		Light light = new Light();
    		
    		//创建调用者
    		RemoteControl MyRemoteControl = new RemoteControl();
    		MyRemoteControl.AddCommand("Fan", new FanOnCommand(fan));
    		MyRemoteControl.AddCommand("Light", new LightOnCommand(light));
    		MyRemoteControl.ExcuteCommand("Light");
    		MyRemoteControl.ExcuteCommand("Fan");
    		MyRemoteControl.UndoCommand("Light");
    		MyRemoteControl.UndoCommand("Fan");
    	}
    }
    

      

    三、命令模式类图

    image

    四、总结

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

    命令模式要点:

    1.Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。 
    2.实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。 
    3.通过使用Compmosite模式,可以将多个命令封装为一个“复合命令”MacroCommand。 
    4.Command模式与C#中的Delegate有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,更符合抽象原则;Delegate以函数签名来定义行为接口规范,更灵活,但抽象能力比较弱。 
    5.使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。

    适用性: 

        在下面的情况下应当考虑使用命令模式: 
    1.使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。 
    2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。 
    3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。 
    4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

  • 相关阅读:
    消息(5)——WSE增强的web服务套件,MTOM附件
    消息(4)——WS附件传输,包体中的base64编码附件
    WCF消息之XmlDictionaryWriter
    Aptana使用技巧—Aptana2.0系列教程
    [f]获取URL中的参数[转]
    [f]获取元素在页面的位置getPos
    手机wap2.0网页缩放设置
    WPF SnapsToDevicePixels作用
    Silverlight获取控件绝对位置
    浏览器: F5 和 Ctrl+F5的区别
  • 原文地址:https://www.cnblogs.com/snowbook/p/5158605.html
Copyright © 2011-2022 走看看