• 设计模式--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()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

  • 相关阅读:
    matplotlib 进阶之origin and extent in imshow
    Momentum and NAG
    matplotlib 进阶之Tight Layout guide
    matplotlib 进阶之Constrained Layout Guide
    matplotlib 进阶之Customizing Figure Layouts Using GridSpec and Other Functions
    matplotlb 进阶之Styling with cycler
    matplotlib 进阶之Legend guide
    Django Admin Cookbook-10如何启用对计算字段的过滤
    Django Admin Cookbook-9如何启用对计算字段的排序
    Django Admin Cookbook-8如何在Django admin中优化查询
  • 原文地址:https://www.cnblogs.com/snowbook/p/5158605.html
Copyright © 2020-2023  润新知