设计模式系列—命令模式

作者: 呆麻子 | 来源:发表于2016-07-02 21:54 被阅读401次

    《Head First设计模式》读书笔记

    命令模式(封装调用)

    一,场景介绍

    1,需求

    设计一个家电自动化遥控器的API,这个遥控器有7个可编程的插槽,每个插槽都有对应的开和关按钮,这个遥控器还有一个整体的撤销按钮。
      提供的家电厂商类有很多:Light、Door、TV等

    2,思考

    • 对于遥控器,它只是知道按下按钮,然后执行对应的ON或者OFF命令,但是他不知道电器执行的具体细节。
    • 如果采用判断语句if,else来逐个判断,这样必然违背了设计的基本思想。
    • 命令模式刚好符合这个场景,命令模式可以将动作请求(命令)从对象执行对应的操作完全解耦出来,那么学习和理解命令模式就是接下来的重点。

    二,介绍命令模式

    1,餐厅订餐来理解命令模式

    客户(Client)点餐,然后服务员(Invoker)将点餐内容记录下来形成一个订单(Command),然后服务员将订单交给厨房(Receiver),然后厨房做出餐点。首先需要理解的是,作为调度者的服务员,他只需要形成订单(也就是一个家电控制命令),然后将订单送出去,并不需要了解订单的内容是什么,也不需要由哪个厨师来完成,他只是简单的进行一个传递工作。这就是命令模式中一个重要的思想解耦。

    2,命令模式中需要学习的重点

    在命令模式中,我们不仅要学习命令模式的基本思想结构,我们还要学习,执行基本的单个命令模式,执行多组的命令模式,命令模式的撤销(回退到上一个状态)。最后要知道的就是命令模式的用途。

    3,先实行一个基本的单个命令模式

    先定义一个命令接口,可以让遥控器设置接口对象的时候都是统一的类型。

    public interface Command {
        public void execute();
    }
    

    定义一个Light类,这个是目标,里面设置的有Light.on()方法。

    public class Light {
        public Light(){}
        public void on(){
            System.out.println("Light is on!");
        }
    }
    

    设置一个开灯的命令来包装一下Light对象

    public class LightOnCommand implements Command {
        private Light light;
        public LightOnCommand(Light light) {
            this.light = light;
        }
        @Override
        public void execute() {
            light.on();
        }
    }
    

    设置一个远程命令控制接口,注意思考为什么要设置真么一个控制接口

    public class SimpleRemoteControl {
        private Command comd;
        public SimpleRemoteControl() {  
        }
        public void setCommand(Command command){
            this.comd = command;
        }
        public void buttonWaspressed(){
            comd.execute();
        }
    }
    

    最后一个测试类

    public class Test {
        public static void main(String[] args) {
            SimpleRemoteControl control = new SimpleRemoteControl();
            Light light = new Light();
            LightOnCommand lightCommand = new LightOnCommand(light);
            control.setCommand(lightCommand);
            control.buttonWaspressed();
        }
    }
    

    三,命令模式的结构图

    如下:

    Paste_Image.png

    这个结构图里面多了一个LightOffCommand,其添加方式和上面的简单设计模式实现代码是一样的,而且你还可以添加更多的TVCommand、DoorCommand等,这个可以自己实现。其实到这里我们已经掌握了命令设计模式了,下面只是对命令模式的一些扩展用法介绍,便于在实际开发中灵活扩展。

    四,命令的撤回

    命令撤回,关于撤回,我们可以联想一下我们日常中Ctrl+Z操作,我们很容易想到将命令放到一个堆栈里面,我们只需要挨个弹出来即可实现。
      当然,我们仍然可以按照书中的案例来熟悉一下命令模式。

    1,案例介绍

    比如吊扇(CeilingFan)他有高速、中速、低速、和关闭状态。沃恩需要记住电扇之前的运行状态,可能是上面四种的任意一个,比如低速,现在我们执行电扇,启动高速,撤回(undo)的时候就变为低速。下面请看代码

    2,还是一样,先创建一个Command接口

    public interface Command {
        public void execute();
        public void undo();
    }
    

    3,创建一个CeilingFan类

    public class CeilingFan {
    public static final int HIGH = 3;
    public static final int MEDIUM = 2;
    public static final int LOW = 1;
    public static final int OFF = 0;
    int speed;
    
    public CeilingFan() {
        speed = OFF;
    }
    public void high(){
        speed = HIGH;
        System.out.println("The speed of the ceilingFan is High");
    }
    public void medium(){
        speed = MEDIUM;
        System.out.println("The speed of the ceilingFan is medium");
    }
    public void low(){
        speed = LOW;
        System.out.println("The speed of the ceilingFan is low");
    }
    public void off(){
        System.out.println("The speed of the ceilingFan is off");
        speed = OFF;
    }
    public int getSpeed(){
        return speed;
    }
    }
    

    4,创建一个CeilingFanHighCommand,当然你可以加上MediumCommand、LowCommand。其中undo就是一个撤回命令。

    public class CeilingFanHighCommand implements Command{
    private CeilingFan ceilingFan;
    private int preSpeed;
    
    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
    
    @Override
    public void execute() {
        preSpeed = ceilingFan.getSpeed();
        ceilingFan.high();
    }
    
    @Override
    public void undo() {
        if(preSpeed == CeilingFan.HIGH){
            ceilingFan.high();
        }else if(preSpeed == CeilingFan.MEDIUM){
            ceilingFan.medium();
        }else if(preSpeed == CeilingFan.LOW){
            ceilingFan.low();
        }else if(preSpeed == CeilingFan.OFF){
            ceilingFan.off();
        }
    }   
    }
    

    5,创建RemoteControl类

    public class RemoteControl implements Command{
    Command comd;
    public RemoteControl() {}
    public void setCommand(Command command){
        this.comd = command;
    }
    @Override
    public void execute() {
        comd.execute();
    }
    
    @Override
    public void undo() {
        comd.undo();
    }
    }
    

    6,创建测试类

    public class Test {
    public static void main(String[] args) {
        RemoteControl control = new RemoteControl();
        CeilingFan ceilingFan = new CeilingFan();
        CeilingFanHighCommand ceilingFanHighCommand = new CeilingFanHighCommand(ceilingFan);
        control.setCommand(ceilingFanHighCommand);
        control.execute();
        control.undo();
    }
    }
    

    五,运行一组命令

    有时候,在特使情景的时候,我们可能需要运行一组命令,比如我们回家,我们想一键完成开门,开灯,然后打开电视等着看剧。或者更多复杂命令的组合要求一键同时执行。下面只实现同时开门,开灯,开电视。
      不多说,直接上代码:

    1,Command接口统一类型

    public interface Command {
    public void execute();
    }
    

    2,几个实体类

    public class Light {
    public void on(){
        System.out.println("Light is on!");
    }
    }
    
    public class Door {
    public void open(){
        System.out.println("The door is openning!");
    }
    }
    
    public class TV {
    public void on(){
        System.out.println("the TV is on!");
    }
    }
    

    3,几个实体类对应的打开命令类

    public class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) {
        this.light = light;
    }
    @Override
    public void execute() {
        light.on();
    }
    }
    
    public class DoorOpenCommand implements Command{
    private Door door;
    public DoorOpenCommand(Door door) {
        this.door = door;
    }
    @Override
    public void execute() {
        door.open();
    }
    }
    
    public class TVOnCommand implements Command{
    private TV tv;
    public TVOnCommand(TV tv) {
        this.tv = tv;
    }
    @Override
    public void execute() {
        tv.on();
    }
    }
    

    4,新添加一个AllOnCommand类,注意此处,本来可以硬编码进去,但是为什么没有,体会设计模式的基本原则。

    public class AllOnCommand implements Command{
    private Command[] command;
    public AllOnCommand(Command[] command) {
        this.command = command;
    }
    @Override
    public void execute() {
        for(Command command:command){
            command.execute();
        }
    }
    }
    

    5,测试类

    public class Test {
    public static void main(String[] args) {
        SimpleRemoteControl control = new SimpleRemoteControl();
        Light light = new Light();
        Door door = new Door();
        TV tv = new TV();
        LightOnCommand lightCommand = new LightOnCommand(light);
        DoorOpenCommand doorOpenCommand =new DoorOpenCommand(door);
        TVOnCommand tvCommand = new TVOnCommand(tv);
        Command[] commands ={lightCommand,doorOpenCommand,tvCommand};
        AllOnCommand allCommand = new AllOnCommand(commands);
        control.setCommand(allCommand);
        control.buttonWaspressed();
    }
    }
    

    六,总结

    命令模式主要就是要体会封装调用的思想,在实际开发中,消息队列是一个很常用的一个机制,他们不管队列里面是执行什么任务,网络请求呐或者数据运算呐,完全不用管具体业务,只需要执行命令就可以。比如会用在数据库日志中,通过记录命令来达到日志的各种功能,比如数据库恢复。等等。

    相关文章

      网友评论

      • FFriday:电风扇的undo方法会起作用吗?看起来好像有点问题
        呆麻子:@刮起木屑的风 风扇在实例化的时候在构造方法中初始化了他的speed,可以通过getSPeed获得,然后在风扇命令里面传给了preSpeed这个全局变量,当然必须是在风扇execute开启后的状态下才可以调用undo,因为preSpeed是在execute中进行赋值初始化的。

      本文标题:设计模式系列—命令模式

      本文链接:https://www.haomeiwen.com/subject/nbiqjttx.html