• 设计模式之命令模式(Command Pattern)


    一.什么是命令模式?

    命令模式,封装了方法调用细节,以解耦请求者与执行者,具体流程如下:

    1.从请求者(客户)的角度看

    请求者(客户)发出请求 -> 调用者(系统)构造命令对象封装请求 -> 调用者调用命令对象的指定方法(请求被执行)

    很明显,请求者根本不知道执行者是谁,更不知道具体执行细节。当然请求者本身也并不关心这些,它只要知道请求被执行了就好。

    2.从执行者(低层组件)的角度看

    执行者(低层组件)被调用 -> 执行者调用内部方法(请求被执行)

    同样的,执行者根本不知道请求者是谁,甚至不清楚调用者,不过没关系,执行者只要本本分分的做好本职工作就好了,没必要知道领导的情况。

    3.从调用者(系统)的角度看

    接到请求 -> 创建命令对象封装请求 -> 在适当的时候调用命令对象的动作来执行请求(请求被执行)

    调用者不知道执行者是谁,也不清楚请求者,它只负责构造命令并控制命令被执行,这就足够了。


    从上面可以看出各个对象之间的低耦合关系:

    请求者(客户)与执行者(低层组件)被彻底解耦,作为中间人的调用者也不了解请求者与执行者的具体细节,它们被很好的保护了起来

    这正是我们想要的。

    二.举个例子

    现实世界中任何一个稍微复杂的子系统都应当有一套命令,比如餐馆的运行机制:

    顾客A来到餐馆点一碗面(发出请求) -> 柜台服务员记录下来(创建命令) -> 服务员把小票扔给厨房 -> 厨师C很快做好了一碗面(请求被执行)

    顾客不知道将由谁来做这碗面,柜台服务员也不知道,厨师不知道是谁点了这碗面,只知道做完面就可以休息了

    是不是与命令模式很相像?


    不妨用代码来实现上面的机制

    首先,我们需要一个命令接口,毕竟命令才是命令模式的核心,没有命令,一切都是空想

    package CommandPattern;
    
    /**
     * @author ayqy
     * 定义Command接口
     */
    public interface Command {
    	public abstract void execute();//只需要定义一个统一的执行方法
    }
    

    有了命令还需要执行者,否则只有将军没有小兵,餐馆的执行者当然是厨师:

    package CommandPattern;
    
    /**
     * @author ayqy
     * 定义Chef基类
     */
    public abstract class Chef {
    	//在此定义厨师的公共属性
    	
    	/**
    	 * 定义烹饪方法
    	 */
    	public abstract void cook();
    	//在此定义其它有用的方法
    }
    

    我们还需要实现具体的厨师,术业有专攻:

    做面的厨师:

    package CommandPattern;
    
    /**
     * @author ayqy
     * 定义专业做面的厨师
     */
    public class NoodlesChef extends Chef{
    
    	@Override
    	public void cook() {
    		System.out.println("做好了一碗美味的拉面");
    	}
    }
    

    做饼的厨师:

    package CommandPattern;
    
    /**
     * @author ayqy
     * 定义专业做饼的厨师
     */
    public class PieChef extends Chef{
    
    	@Override
    	public void cook() {
    		System.out.println("做好了一块香喷喷的大饼");
    	}
    }
    

    有了小兵,有了将军,我们还需要一套完整的命令:

    package CommandPattern;
    
    /**
     * @author ayqy
     * 实现具体NoodlesCommand
     */
    public class NoodlesCommand implements Command{
    	private NoodlesChef chef;//专业做面的厨师
    	
    	public NoodlesCommand(){
    		chef = new NoodlesChef();
    	}
    
    	@Override
    	public void execute() {
    		chef.cook();
    		//调用其它需要的方法
    	}
    }
    
    package CommandPattern;
    
    /**
     * @author ayqy
     * 实现具体PieCommand
     */
    public class PieCommand implements Command{
    	private PieChef chef;//专业做饼的厨师
    	
    	public PieCommand(){
    		chef = new PieChef();
    	}
    
    	@Override
    	public void execute() {
    		chef.cook();
    		//调用其它需要的方法
    	}
    }
    

    备工作做好了,餐馆可以开张了

    三.效果示例

    需要一个Test类:

    package CommandPattern;
    
    /**
     * @author ayqy
     * 实现测试类
     */
    public class Test {
    	
    	public static void main(String[] args) {
    		System.out.println("Command Pattern餐馆开张。。");
    		System.out.println("第一位客户X先生");
    		System.out.println("X先生:你好,我需要一碗面,我饿极了");
    		NoodlesCommand nCmd = new NoodlesCommand();
    		System.out.println("柜台服务员:好的,我已经记下了,马上就好");
    		System.out.println("柜台服务员:厨房~~,接单");
    		nCmd.execute();
    		System.out.println("X先生:真快啊!");
    		
    		System.out.println();
    		
    		System.out.println("第二位客户XX先生");
    		System.out.println("XX先生:你好,我需要一块饼,20分钟后来取");
    		PieCommand pCmd = new PieCommand();
    		System.out.println("柜台服务员:好的,我已经记下了");
    		System.out.println("15分钟后");
    		System.out.println("柜台服务员:厨房~~,接单");
    		pCmd.execute();
    		System.out.println("XX先生:真准时啊!");
    	}
    }
    

    结果示例:


    从例子可以看出:

    1.调用者(柜台服务员)可以控制具体执行时机,但对具体执行者(厨师)的细节完全不清楚

    2.请求者(顾客)完全不知道餐馆的运行机制,不知道点的餐是厨师做的还是服务员做的或者是从隔壁买的。。

    3.执行者(厨师)完全不知道请求者的情况,它只做了本职工作,其它的什么都不知道

    四.命令模式的扩展

    1.宏命令(多条命令顺序执行)

    我们可以定义“命令的命令”来实现(这种特殊的命令的execute方法内部是顺序调用其它若干命令的execute方法。。)

    2.撤销

    假如来了很多顾客,点了很多份餐点,过了一会儿有几个顾客等不及了需要撤销,我们如何实现?

    维护一个命令列表,记录已经创建的命令,撤销时需要找到对应的命令,执行撤销操作

    当然,前提是命令对象支持撤销,我们需要做一些修改:

    package CommandPattern;
    
    /**
     * @author ayqy
     * 定义Command接口
     */
    public interface Command {
    	public abstract void execute();//只需要定义一个统一的执行方法
    	
    	public abstract void undo();//定义统一的撤销方法
    }
    

    各个命令的撤销操作可能不同,因此定义为抽象方法,由子类来实现具体操作

    *如何支持多步顺序撤销?

    餐馆的例子可能不需要这样的功能,不妨想想另一个情景,文本编辑器

    用户发出了一系列命令,完成了一些列操作,后来发现并不需要这样做,用户会撤销修改(Ctrl + Z),这时我们需要执行相反的操作对内容作以还原,要如何实现?

    还是要先实现各个命令的undo行为(执行与execute相反顺序的操作即可),除此之外,我们还需要一个栈来记录已经被执行过的操作,以支持撤销到初始状态

    3.队列请求

    我们可以建立一个工作线程,负责所有运算,想象有一个通道,输入是一条条不同命令,输出是命令的执行结果

    可能上一刻工作线程在做大饼,下一刻已经出去买菜了。。

    *这样做有什么好处?

    可以把运算限制在指定的几个线程中,加以控制

    4.日志请求

    多用于数据库管理系统的实现,我们需要把一系列的操作记录下来(如写在硬盘上),在遇到系统故障时读出来以恢复数据,如何实现?

    利用对象的序列化把对象保存起来(记录日志),在需要的时候反序列化(恢复事务)

    五.总结

    命令模式可以有效地解耦请求者与执行者,还可以提供一些额外的好处(比如支持撤销操作、队列请求、记录日志等等)

  • 相关阅读:
    Freezing Your Tuples Off 之 vacuum_freeze_min_age
    Understanding virtualxid && transactionid
    PostgreSQL and bloat
    FSM, VISIBILITY MAP AND VACUUM
    Heap Only Tuples (HOT)
    Measuring PostgreSQL Checkpoint Statistics
    PgSQL · 特性分析 · 谈谈checkpoint的调度
    TypeError: Unexpected keyword argument passed to optimizer: amsgrad原因及解决办法
    kitti 数据集解析
    ubuntu16.04 跑Apollo Demo
  • 原文地址:https://www.cnblogs.com/ayqy/p/3969506.html
Copyright © 2020-2023  润新知