设计模式系列—命令模式

《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();
}
}

六,总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容