问题引入
近来,智能家居闹得比较凶,这里我们想要实现一个简单的自动家居,由一个遥控器来完成电灯、音响、风扇的开关。
模式定义
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作
认识模式
对于这个模式我最欣赏的就是他将“做什么”与"怎么做"的问题或者说是“动作的请求者”与"动作的实现者”进行了完美的解耦。并且,它支持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();
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() {
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() {
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(){}
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;
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];
in_undo_satck.push(undoCom);
}
public void pressOff( int i)
{
offcoms[i].execute();
undoCom = offcoms[i];
in_undo_satck.push(undoCom);
}
public void Pressundo()
{
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) {
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);
ControlBar2 clb = new ControlBar2();
clb.setCommand( 0 , com1, com2);
clb.setCommand( 1 , com3, com4);
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啦!
模式延伸
对于大多数请求-响应模式的功能,比较适合使用命令模式。正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。