zoukankan      html  css  js  c++  java
  • 设计模式(七):命令模式

    一、概述

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

    二、解决问题

      假设我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。那么问题来了,这些智能家电来自不同的厂家,我们是不是要对每个厂家的家电都安装一个app呢?毫无疑问,我们肯定不想安装那么多的app,我们希望只要一个app就可以控制全部智能家电。要实现一个app控制所有智能家电的需要,每个智能家电厂家都要提供一个统一的接口给app调用,这个时候命令模式就可以发挥作用了。命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来,在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品。

    三、结构类图

      

     

    四、成员角色

      客户端(Client):创建具体命令对象,并设置命令对象的接收者。

      调用者(Invoke):持有命令对象的引用,在某个时间点执行命令对象的方法。

      命令接口(Command):定义命令对象的共有的方法,具体命令对象必须实现这些方法。

      具体命令对象(ConcreteCommand):实现命令接口的方法,操纵接收者的执行相关动作。

      接收者(Receiver):请求的执行者,其方法被具体命令对象调用。

    五、应用实例

       好了,让我们来简化一下手机app,假如它的界面如下所示,我们可以设置每一行按键对应的电器,我们设置第一行按键为电灯的开关,第二行按键为电视开关。下面就实现这个app的功能。

      

    第一步,创建电灯和电视对象,就是接收者

    package command.pattern;
    
    //灯对象
    public class LightObj {
    	public void on(){
    		System.out.println("灯打开了");
    	}
    	
    	public void off(){
    		System.out.println("灯关闭了");
    	}
    }
    

      

    package command.pattern;
    
    //电视对象
    public class TVObj {
    	public void on(){
    		System.out.println("电视打开了");
    	}
    	
    	public void off(){
    		System.out.println("电视关闭了");
    	}
    }
    

      第二步,创建命令接口和空命令

    package command.pattern;
    
    //定义命令接口
    public interface Command {
    	//执行动作
    	public void execute();
    	//撤销动作
    	public void undo();
    }
    

      

    package command.pattern;
    
    //没有任何命令,用于初始化每个按键,当调用空命令时,对象什么事情都不做,
    //有些时候这也是一种设计模式,因为它为我们省去判空的操作
    public class NoCommand implements Command{
    
    	public void execute() {
    		
    	}
    
    	public void undo() {
    		
    	}	
    }

      第三步、创建电灯开关命令

    package command.pattern;
    
    //打开电灯命令
    public class LightOnCommand implements Command{
    	LightObj light;
    
    	public LightOnCommand(LightObj light) {
    		super();
    		//设置接收者
    		this.light = light;
    	}
    
    	//执行请求
    	public void execute() {
    		//调用接收者的方法
    		light.on();		
    	}
    
    	//撤销请求
    	public void undo() {
    		light.off();	
    	}	
    }
    

      

    package command.pattern;
    
    //电灯的关闭命令
    public class LightOffCommand implements Command{
    	LightObj light;
    		
    	public LightOffCommand(LightObj light) {
    		super();
    		//设置接收者
    		this.light = light;
    	}
    
    	//执行请求
    	public void execute() {
    		light.off();
    		
    	}
    
    	//撤销请求
    	public void undo() {
    		light.on();	
    	}
    }
    

      第四步、创建电视开关命令

    package command.pattern;
    
    //打开电视命令
    public class TVOnCommand implements Command{
    	TVObj tv;
    	
    	public TVOnCommand(TVObj tv){
    		this.tv = tv;
    	}
    	
    	//执行请求
    	public void execute() {	
    		tv.on();
    	}
    
    	//撤销请求
    	public void undo() {
    		tv.off();
    	}
    }
    

      

    package command.pattern;
    
    //关闭电视命令
    public class TVOffCommand implements Command{
    	TVObj tv;
    	
    	public TVOffCommand(TVObj tv){
    		this.tv = tv;
    	}
    	
    	//执行请求
    	public void execute() {
    		tv.off();
    	}
    
    	//撤销请求
    	public void undo() {
    		tv.on();	
    	}
    }
    

      第五步、创建调用者

    package command.pattern;
    
    //调用者
    public class RemoteControl {
    	//开按钮命令数组
    	Command[] onCommands;
    	//关按钮命令数组
    	Command[] offCommands;
    	//撤销执行的命令
    	Command undoCommand;
    	
    	public RemoteControl(){
    		onCommands = new Command[5];
    		offCommands = new Command[5];
    		
    		for(int i = 0;i < 5;i++){
    			onCommands[i] = new NoCommand();
    			offCommands[i] = new NoCommand();
    		}
    	}
    	
    	public void setCommand(int slot,Command onCommand,Command offCommand){
    		onCommands[slot] = onCommand;
    		offCommands[slot] = offCommand;
    	}
    	
    	//按下开按钮
    	public void onButtonWasPushed(int slot){
    		onCommands[slot].execute();
    		//保存最后一次操作,用于撤销操作
    		undoCommand = onCommands[slot];
    	}
    	
    	//按下关按钮
    	public void offButtonWasPushed(int slot){
    		offCommands[slot].execute();
    		//保存最后一次操作,用于撤销操作
    		undoCommand = offCommands[slot];
    	}
    	
    	public void undoButtonWasPushed(){
    		undoCommand.undo();
    	}
    }
    

      第六步、测试手机app

    package command.pattern;
    
    public class RemoteControlTest {
    	public static void main(String[] args){
    		//创建电灯对象
    		LightObj light = new LightObj();
    		//创建电视对象
    		TVObj tv = new TVObj();
    		//创建电灯和电视的开关命令
    		Command lingOnCommand = new LightOnCommand(light);
    		Command lingOffCommand = new LightOffCommand(light);
    		Command tvOnCommand = new TVOnCommand(tv);
    		Command tvOffCommand = new TVOffCommand(tv);
    		
    		RemoteControl remote = new RemoteControl();
    		//把第一行的按钮设置为电灯的开关
    		remote.setCommand(0, lingOnCommand, lingOffCommand);
    		
    		System.out.println("-----按下灯的开按钮-----");
    		remote.onButtonWasPushed(0);
    		System.out.println("-----按下灯的关按钮-----");
    		remote.offButtonWasPushed(0);
    		System.out.println("-----按下撤销键-----");
    		remote.undoButtonWasPushed();
    		
    		//把第二行的按钮设置为电视的开关
    		remote.setCommand(1, lingOnCommand, lingOffCommand);
    		System.out.println("-----------------------");
    		System.out.println("-----按下电视的开按钮-----");
    		remote.onButtonWasPushed(1);
    		System.out.println("-----按下电视的关按钮-----");
    		remote.offButtonWasPushed(1);
    		System.out.println("-----按下撤销键-----");
    		remote.undoButtonWasPushed();
    		
    		
    	}
    }
    

      运行结果:

    六、优点和缺点

      1、优点

      (1)、将发起请求的对象与执行请求的对象解耦。发起请求的对象时调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作。就拿上面的手机app例子来说,不管什么品牌的智能电器,你只要实现统一的命令接口,并告诉手机app你的具体命令什么,手机app就可以控制你的工作,而不必知道你是什么品牌的电器。

      (2)、有很好的扩展性。新增一个具体命令对象不会影响到其他命令对象,所以很容易增加一个具体命令类。

      (3)、很容易地设计一个命令队列。只要把命令对象放到列队,我们就可以多线程的执行命令。

      (4)、可以容易地实现对请求的撤销和重做。

      (5)、可以实现命令日志,当需要系统瘫痪时直接从命令日志恢复系统。

      2、缺点

      (1)、由于一个命令对应一个接收者动作,当接收者动作过多时,会创建很多命令对象,增加了系统的复杂度。

    七、使用场合

      (1)、当需要一个对象执行某个动作,但并不知道或者无须知道接收者是什么对象时使用。

      (2)、当需要连续或者同时控制多个对象执行动作时,可以运用命令模式把每个动作封装成一个命令,组成命令队列。只要操纵该队列的命令就可以控制多个对象执行相关动作。

      (3)、当需要考虑系统恢复时,可以使用命令日志。

    八、总结

      1、”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。

      2、空命令也可以说是一种设计模式,它为我们省去了判空的操作。在上面的应用实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦,你不判空的话,程序可能会空指针异常,判空又会带来很多重复的操作。

    //按下开按钮,判空
    	public void onButtonWasPushedWithNull(int slot){
    		//判断命令是否为空
    		if(onCommands[slot] != null){
    			onCommands[slot].execute();
    			//保存最后一次操作,用于撤销操作
    			undoCommand = onCommands[slot];
    		}	
    	}

     

  • 相关阅读:
    JavaScript Eval 函数使用
    WPFToolkit Calendar & DatePicker 使用介绍
    Windows Mobile 6.5 配置环境,数据库访问,部署简单实例
    ThreadPool.QueueUserWorkItem 方法 (WaitCallback)
    Windws Mobile 6.5 Professional ADO.NET数据访问
    WPF调用Web Services
    c#中Interface的理解
    PagesSection.MaintainScrollPositionOnPostBack 属性
    EclipseRCP中文语言包版本不一致,导致导出错误
    SWT美化开源控件网站
  • 原文地址:https://www.cnblogs.com/jenkinschan/p/5731092.html
Copyright © 2011-2022 走看看