设计模式(六)The Command Pattern 命令模式

摘要 命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作

问题引入

近来,智能家居闹得比较凶,这里我们想要实现一个简单的自动家居,由一个遥控器来完成电灯、音响、风扇的开关。

模式定义

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作

认识模式

对于这个模式我最欣赏的就是他将“做什么”与"怎么做"的问题或者说是“动作的请求者”与"动作的实现者”进行了完美的解耦。并且,它支持undo的操作。

问题解决

命令模式实现步骤:

* 1、定义一个Command接口,这个接口中只含有execute()方法;

* 2、定义各个命令对象(需要实现上述的Command接口),它包含具体实现功能的对象引用,并在execute()方法中定义相应的操作;

* 3、将这个命令对象交给”动作请求者“。

直接上代码:

一、不带undo的遥控器

1>这是Command接口

?


1

2

3

4

5

6

7

package my.oschina.net.design.commands;

public interface Com {

    public void execute();

    //这个undo()撤销方法我们一会儿再说

    public void undo();

}

2>我们来定义一个开(关)灯的命令

先看看这个灯的类吧

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

package my.oschina.net.design.commands;

/**

 * 他包括灯的一切操作(关灯、开灯)

 * @author Eswin

 *

 */

public class Light

{

    public Light(){}

    

    public void lightOn()

    {

        System.out.println("The light is on !");

    }

            

    public void lightOff()

    {

        System.out.println("The light is off !");

    }

}

这是开关灯的命令对象

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

package my.oschina.net.design.commands;

public class LightOnCommand implements Com{

    private Light light;

    

    public LightOnCommand(Light light)

    {

        this.light = light;

    }

    

    public void execute()

    {

        light.lightOn();

    }

    @Override

    //显然开灯的撤销就是要关灯

    public void undo() {

        // TODO Auto-generated method stub

        light.lightOff();

    }      

}

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

package my.oschina.net.design.commands;

public class LightOffCommand implements Com{

    private Light light;

    

    public LightOffCommand(Light light)

    {

        this.light = light;

    }

    

    public void execute()

    {

        light.lightOff();

    }

    @Override

    //显然开灯的撤销就是要开灯

    public void undo() {

        // TODO Auto-generated method stub

        light.lightOn();

    }

}

同理对于fans也是一样的

3>是时候告诉”接收者“遥控器他可以实现的命令了

这是一个简单遥控器类(只有一个按钮,我们只是来测试这个命令,下面有个更全面的例子)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package my.oschina.net.design.commands;

public class ControlBar {

    //命令对象的引用

    Com com;

    

    public ControlBar(){}

    

    /*

     * 通过传入Com参数决定遥控器可以实现的命令

     */

    public void setCommand(Com com)

    {

        this.com = com;

    }

    

    //一旦我们按下按钮(下命令),就有真正的接受者完成相应的任务,我们的遥控器并不需要知道到底是如何实现的

    public void pressDown()

    {

        com.execute();

    }

}

Test一下吧

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

package my.oschina.net.design.commands;

public class TestCom {

    

    public static void main(String[] args)

    {

        Light light = new Light(); 

        Com com1 = new LightOnCommand(light);

        Com com2 = new LightOffCommand(light);

        

        Fans fans = new Fans();

        Com com3 = new FansOnCommand(fans);

        Com com4 = new FansOffCommand(fans);

        

        ControlBar clb = new ControlBar();

        clb.setCommand(com1);

        clb.pressDown();

        

        clb.setCommand(com2);

        clb.pressDown();

        

        clb.setCommand(com3);

        clb.pressDown();

        

        clb.setCommand(com4);

        clb.pressDown();   

    }

}

可以看到我们设置好命令,然后只需要PressDown按钮,就可以完成相应的命令

结果如下:

二、带有undo的遥控器

这是一个很炫酷的功能。你肯定遇到过这样尴尬的情况,当你做下一个决定的那一刻你就发现自己后悔了(很奇妙),怎么办???没事,我们提供了undo功能!

undo说到底很简单,就是撤销上步操作(没错,就是ctrl + z),换句话说,就是执行与你上一步的动作相反的动作(你开了我就关)

回头看看LightOnCommand和LightOffCommand命令中的undo操作!确实很简单!

好了,我们可以在遥控器上加上这个硬件按钮了。

接下来我们创建一个真实的遥控器,遥控器上有多个控制按钮,分别实现各个功能。

这是遥控器类:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

package my.oschina.net.design.commands;

import java.util.Stack;

public class ControlBar2 {

        //我们将命令放在数组中更加清晰一点

    Com[] oncoms;

    Com[] offcoms;

    

    //这是个撤销命令对象的引用

    Com undoCom;

    

    //用栈来保存每次需要撤销的操作,从而可以一直undo(当然也是有一定次数限制的)

    public Stack<Com> in_undo_satck = new Stack<Com>();

    

    ControlBar2()

    {

        //记得生成这个数组,要不然出现空指针的现象

        oncoms = new Com[3];

        offcoms = new Com[3];

    }

    /**

     

     * @param i 遥控器的命令插槽,用于标识控制的是什么(电灯?电扇?)

     * @param oncom 控制对象的开命令

     * @param offcom 控制对象的关命令

     */

    public void setCommand(int i, Com oncom, Com offcom)

    {  

        oncoms[i] = oncom;

        offcoms[i] = offcom;     

    }

    

    //开按钮

    public void pressOn(int i)

    {

        oncoms[i].execute();

        记录下上一次执行的动作,

        undoCom = oncoms[i];

        

        //将上一次将执行的动作记录到栈中,一会弹出一次调用undo()方法

        in_undo_satck.push(undoCom);

        

    }

    

    //关按钮

    public void pressOff(int i)

    {

        offcoms[i].execute();

        undoCom = offcoms[i];

        

        //将上一次将执行的动作记录到栈中,一会弹出一次调用undo()方法

        in_undo_satck.push(undoCom);

        

    }  

    

    /*带参数的undo撤销按钮,根据传入的i(标识控制哪个对象),对相应的开操作做撤销,这个可以不要

    public void Pressundo(int i)

    {

        oncoms[i].undo();  

        in_undo_satck.push(undoCom);

    }

    */

    

    //undo撤销按钮(终于见到你了)

    public void Pressundo()

    {

            //将上一次将执行的动作记录到栈中,一会弹出一次调用undo()方法,撤销的命令也可以压入栈中,进行撤销操作

        if(!in_undo_satck.isEmpty())       

            in_undo_satck.pop().undo();

    }

}

好了,Test一下

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

package my.oschina.net.design.commands;

public class TestControlBar2 {

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        //创建一个Light对象

        Light light = new Light();

        //用Light对象初始化LightOnComman与LightOffCommand命令对象

        Com com1 = new LightOnCommand(light);

        Com com2 = new LightOffCommand(light);

        

        //创建一个Fans对象

        Fans fans = new Fans();

        //用Light对象初始化FansOnComman与FansOffCommand命令对象

        Com com3 = new FansOnCommand(fans);

        Com com4 = new FansOffCommand(fans);

        

        //这是一个遥控器

        ControlBar2 clb = new ControlBar2();   

        //在各个插槽上绑定命令   

        clb.setCommand(0, com1, com2);

        clb.setCommand(1, com3, com4);

        

        //记住了我们绑定的命令0上绑定的是对Light的操作

        clb.pressOn(0);

        clb.pressOff(0);

        

        clb.pressOn(1);

        clb.pressOff(1);

        clb.pressOn(1);

        clb.pressOn(0);

        

        clb.pressOn(1);

        clb.pressOff(1);

        System.out.println("UNDO ------------> ");

        while( clb.in_undo_satck.iterator().hasNext())

            clb.Pressundo();

            

    }

        

}

预测结果:

从分割线(UNDO --------------->)处,两边是成镜像对称的。

实际结果(丝毫不差):

好了,到这里为止,命令模式差不多了。

不过这里还有一个问题,就是风扇的撤销是不是太过简陋了?的确,风扇除了开就是关?显然不是嘛!我们可是可以调风速的呀!

这个问题其实也很好解决,我们在只要在设计风扇的命令的时候添加一个Speed的字段,记录下设置风速之前的Speed,撤销的时候再调回去就OK啦!

模式延伸

对于大多数请求-响应模式的功能,比较适合使用命令模式。正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。

时间: 2024-11-06 12:10:40

设计模式(六)The Command Pattern 命令模式的相关文章

(Command Pattern)命令模式

定义 将“请求”封装成对象,以便使用不同的请求.队列或者日志来参数化其他对象.命令模式也支持可撤销的操作. 结构图: 命令模式的角色划分: Reciever(命令的接收者):接收命令,并知道如何进行必要的工作,实现具体的处理.任何类都可以当接收者. Invoker(命令调用者):命令调用者持有一个命令对象,并在某个时间点调用命令对象的Execute()方法,将请求付诸实行. Command(命令接口):Command为所有命令声明了一个接口.调用命令对象的Execute()方法,就可以让接收者进

Command Pattern 命令模式

定义: 命令模式将‘请求’封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象,命令模式也支持可撤销的操作. 类图 如上图所示:Command类是用来声明执行操作的接口:ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现Execute:Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可能作为一个接收者:Invoker类,要求该命令执行这个命令. 命令模式实现: /** *声明执行者接口 */ public abstract

[Design Pattern] Command Pattern 命令模式

发现公司的代码好像有用到 Command Pattern,回顾重温下. Command Pattern 的类图结构如下: 参考 <Head First Design Patterns(英文版)>P245 绘制 我所理解的 Command Pattern 如图. caller, action, subject 分别对于类图中的 Invoker, Command, Receiver, 他们都可以由使用者 Client 进行配置 参考资料: Head First Design Pattern. 推荐

设计模式(一):命令模式(1)——基本的命令模式

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.生活中的命令模式 1.案例 如果将命令模式反映到生活中,遥控器无疑是一个很好的例子.假如我们有如下一个遥控器 这个遥控器有三个插槽(编号为0,1,2),每个插槽对应着要操作的一个电器,插槽所控制的电器这里设置的分别是卧室灯.空调.冰箱,这些电

设计模式(一):命令模式(2)——命令模式扩展之宏命令

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.升级遥控器 1.需求 经过上一节的设计,我们的遥控器每个按键都具备了基本的开关功能,还有一个按键具备了撤销功能.但人类都是懒惰的,现在我们想要按下0号插槽的On按钮时就可以将电灯.空调.洗衣机打开,按下0号插槽的Off按钮时就可以将电灯.空调

设计模式(一):命令模式(4)——命令模式扩展之日志请求

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.命令模式扩展之日志请求 1.日志请求的工作方式 上一节介绍了命令模式之队列请求,本节介绍一下另一个扩展——日志请求.某些用用需要将我们所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态.命令模式能够支持这一点.这些

设计模式(一):命令模式(3)——命令模式扩展之队列请求

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.命令模式扩展——队列请求 1.队列请求的工作方式 上一篇说了命令模式的扩展之宏命令,本节讲解一下命令模式的第二个扩展队列请求.因为命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样.现在,即使命令被创建许久之

.NET设计模式(17):命令模式(Command Pattern)(转)

概述 在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”.但在某些场合,比如要对行为进行“记录.撤销/重做.事务”等处理,这种无法抵御变化的紧耦合是不合适的.在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合[李建忠].这就是本文要说的Command模式. 意图 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤消的操作.[GOF <设计模式>] 结构图 Command模式

读书笔记_java设计模式深入研究 第十章 命令模式 Command

1,命令模式:主要针对需要执行的任务或用户提出的请求进行封装与抽象.抽象的命令接口描述了任务或请求的共同特征,而实现交由不同的具体命令对象完成.每个命令对象都是相互独立的,它负责完成需要执行的任务,却并不关心是谁调用的. 2,UML模型: 3,角色分析: -1,ICommander:抽象命令者,是一个接口,规定了用来封装请求的若干个方法. -2,ConcreteCommander:具体命令发送者,即命令源.实现命令接口. -3,Invoke:请求者,具体命令的管理和维护类.请求者是包含一个"命令