设计模式--6 命令模式

本文为转载学习

原作者:gnuhpc 

原链接:http://www.cnblogs.com/gnuhpc/

1、本节例子是创建一个类似智能家居的万能遥控器,控制各种家电。我们需要将“请求”封装成对象(一个命令对象通过在特定接受者上绑定一组动作来封装请求),以便使用不同的请求、队列、或者日志来参数化其对象--这即为命令模式

2、一个具体的例子

首先完成对命令的对象封装:

1 public interface Command { 
2     public void execute(); 
3 }

只有一个方法,所有的具体命令对象都要实现这个接口,这就做到了封装,如灯这个对象,

 1 public class Light {
 2 
 3     public Light() { 
 4     }
 5 
 6     public void on() { 
 7         System.out.println("Light is on"); 
 8     }
 9 
10     public void off() { 
11         System.out.println("Light is off"); 
12     } 
13 }

我们可以通过上述接口封装“开灯”这个命令,这个就是所谓的命令对象,它把动作和接受者包进对象之中,只暴露一个execute方法:

1 public class LightOnCommand implements Command { 
2     Light light; 
3     public LightOnCommand(Light light) { 
4         this.light = light; 
5     } 
6     public void execute() { 
7         light.on(); 
8     } 
9 }

而我们对于上述封装要一无所知,才能做到解耦:

 1 public class SimpleRemoteControl { 
 2     Command slot; 
 3     public SimpleRemoteControl() {} 
 4     public void setCommand(Command command) { 
 5         slot = command; 
 6     } 
 7     public void buttonWasPressed() { 
 8         slot.execute(); 
 9     } 
10 }

现在试着用一些这个遥控器,首先创建一个遥控器,然后创建开灯这个命令并置于其中,最后安歇按键,这样,遥控器不知道是哪个对象(实际上是灯),进行了哪个动作(实际上是开灯这个动作)就可以完成请求的发出。如此一来,遥控器和灯之间的耦合性就非常低了:

1 public class RemoteControlTest { 
2     public static void main(String[] args) { 
3         SimpleRemoteControl remote = new SimpleRemoteControl(); 
4         Light light = new Light(); 
5         LightOnCommand lightOn = new LightOnCommand(light); 
6         remote.setCommand(lightOn); 
7         remote.buttonWasPressed(); 
8         } 
9 }

我们想要在一个遥控器中实现控制多个家电的能力就可以用一个数组来维护这样的一种命令,可以看做提供了多个命令的插槽:

 1 public class RemoteControl { 
 2     Command[] onCommands; 
 3     Command[] offCommands; 
 4     public RemoteControl() { 
 5         onCommands = new Command[7]; 
 6         offCommands = new Command[7]; 
 7         Command noCommand = new NoCommand(); 
 8         for (int i = 0; i < 7; i++) { 
 9             onCommands[i] = noCommand; 
10             offCommands[i] = noCommand; 
11         } 
12     } 
13     public void setCommand(int slot, Command onCommand,Command offCommand) { 
14         onCommands[slot] = onCommand; 
15         offCommands[slot] = offCommand; 
16     } 
17     public void onButtonWasPushed(int slot) { 
18         onCommands[slot].execute(); 
19     } 
20     public void offButtonWasPushed(int slot) { 
21         offCommands[slot].execute(); 
22     } 
23     public String toString() { 
24     } 
25 }                    

本例使用noCommand的对象出现在初始化遥控器对象,这是一个小技巧,我们并不像每次都检查某个插槽是不是绑定了命令,我们就可以进入这个东西,实现一个不做事情的命令:

1 public class NoCommand implements Command { 
2     public void execute() { } 
3 }

这样初始化就让每一个插槽都有命令,以后若不进一步指明命令的插槽,那么就是默认这个noCommand对象。这就是一个典型的“空对象”例子,当你不想返回一个有意义的对象时,空对象就十分有用,此时空对象可以做成判断null或者提示“未绑定”信息的工作。

3、在命令模式中也可以设置类似undo的撤销命令发出命令的请求:

1 public interface Command { 
2     public void execute(); 
3     public void undo(); 
4 }

使用电灯这个操作来说明:对于一个开灯命令,其对应的撤销命令自然是“关电灯”:

 1 public class LightOnCommand implements Command { 
 2     Light light; 
 3     public LightOnCommand(Light light) { 
 4         this.light = light; 
 5     } 
 6     public void execute() { 
 7         light.on(); 
 8     } 
 9   public void undo() { 
10         light.off(); 
11     } 
12 }

但是,我们必须让遥控器记住上次到底做了什么操作,才可能去撤销:

package headfirst.command.undo;

import java.util.*;
// 
// This is the invoker 
// 
public class RemoteControlWithUndo { 
    Command[] onCommands; 
    Command[] offCommands; 
    Command undoCommand; 
    public RemoteControlWithUndo() { 
        onCommands = new Command[7]; 
        offCommands = new Command[7]; 
        Command noCommand = new NoCommand(); 
        for(int i=0;i<7;i++) { 
            onCommands[i] = noCommand; 
            offCommands[i] = noCommand; 
        } 
        undoCommand = 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();//发出撤销命令 
    } 
    public String toString() { 
    } 
}                    

倘若对于多种状态,如吊扇有多个转速,高中低关四个状态:

public class CeilingFan { 
    String location = ""; 
    int level; 
    public static final int HIGH = 2; 
    public static final int MEDIUM = 1; 
    public static final int LOW = 0; 
    public CeilingFan(String location) { 
        this.location = location; 
    } 
    public void high() { 
        // turns the ceiling fan on to high 
        level = HIGH; 
        System.out.println(location + " ceiling fan is on high"); 
    }

    public void medium() { 
        // turns the ceiling fan on to medium 
        level = MEDIUM; 
        System.out.println(location + " ceiling fan is on medium"); 
    }

    public void low() { 
        // turns the ceiling fan on to low 
        level = LOW; 
        System.out.println(location + " ceiling fan is on low"); 
    } 
    public void off() { 
        // turns the ceiling fan off 
        level = 0; 
        System.out.println(location + " ceiling fan is off"); 
    } 
    public int getSpeed() { 
        return level; 
    } 
}

在实现风扇哥哥转速命令时,就要去记录在执行这个命令前风扇的转速,其撤销命令中啧根据记录的部分完成undo操作。

public class CeilingFanHighCommand implements Command { 
    CeilingFan ceilingFan; 
    int prevSpeed;

    public CeilingFanHighCommand(CeilingFan ceilingFan) { 
        this.ceilingFan = ceilingFan; 
    } 
    public void execute() { 
        prevSpeed = ceilingFan.getSpeed(); 
        ceilingFan.high(); 
    } 
    public void undo() { 
        switch (prevSpeed) { 
            case CeilingFan.HIGH:
                ceilingFan.high(); break; 
            case CeilingFan.MEDIUM:
                ceilingFan.medium(); break; 
            case CeilingFan.LOW:     
                ceilingFan.low(); break; 
            default:                 
               ceilingFan.off(); break; 
        } 
    } 
}                        

在这个例子中,还可以将电灯和电风扇等抽象为电器这个抽象类,然后启开关等两种电器的通用操作就可以写一个命令类完成,而不需要单独写“开电灯”“开电扇”这样的针对特定电器的命令类了。

命令模式可以用于工作队列和日志操作等方面。

 

posted @ 2014-01-18 16:45  未知  阅读(88)  评论(0)    收藏  举报