1.什么是命令模式?
命令模式将请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。 命令模式也支持可撤销的操作。
2.通过具体的需求理解命令模式
2.1 一份来自家电自动化公司的需求
你好,我们自动化公司希望贵公司为我们设计一个家电自动化遥控器的API。我们将附上一个创新遥控器的原型供您研究,这个遥控器有七个可编程的插槽(每个都可以指定到一个不同的家电装置),每个插槽都有对应的开关按钮。 这个遥控器还具备了一个整体的撤销按钮。
我们也在文件里附上一组Java类,这些类是由多家厂商开发出来的,用来控制家电自动化装置,例如电灯,风扇,热水器,音响设备或者其他类似装置。
希望贵公司能够创建一组控制器的API,让每个插槽都能控制一个或一组装置的开关。
下面是我司遥控器原型:
如图所示,此遥控器有7个编程槽,每个编程槽带有两个按钮。 还有一个撤销按钮,全局生效,一共15个按钮。
下面是一些电器厂商提供的类:
现在我们的需求的就是让七个卡槽对上面所有的电器都支持开关并支持撤销操作。
2.2 如何实现这个需求?
经理决定将这个需求设计交给员工A来做,员工A信誓旦旦地说: 没问题,交给我!!
两天后,员工A激动的拿着自己的设计去找经理:
并且带来了演示程序:
演示程序源码地址
经理看了看设计,也看了一下代码,功能确实满足了,但是他就下面代码提出了一个疑问:
private void pressButton(Object obj,boolean isOn){
Class<?> cl = obj.getClass();
if(cl.isAssignableFrom(CeilingFan.class)){
CeilingFan cei = (CeilingFan) obj;
if(isOn){
cei.low();
}else{
cei.off();
}
}else if(cl.isAssignableFrom(CeilingLight.class)){
CeilingLight cei = (CeilingLight) obj;
if(isOn){
cei.on();
}else {
cei.off();
}
}else if(cl.isAssignableFrom(GardenLight.class)){
GardenLight gardenLight = (GardenLight) obj;
if(isOn) gardenLight.manualOn();
else gardenLight.manualOff();
}else if(cl.isAssignableFrom(Light.class)){
Light light = (Light) obj;
if (isOn) {
light.on();
} else {
light.off();
}
}else if(cl.isAssignableFrom(OutdoorLight.class)){
OutdoorLight outdoorLight = (OutdoorLight) obj;
if(isOn) outdoorLight.on();
else outdoorLight.off();
}else if(cl.isAssignableFrom(Sprinkler.class)){
Sprinkler sprinkler = (Sprinkler) obj;
if(isOn) sprinkler.waterOn();
else sprinkler.waterOff();
}else if(cl.isAssignableFrom(Stereo.class)){
Stereo stereo = (Stereo) obj;
if(isOn) stereo.on();
else stereo.off();
}else if(cl.isAssignableFrom(TV.class)){
TV tv = (TV) obj;
if(isOn) tv.on();
else tv.off();
}
}
如果按你这么做的话,那么只要有新的厂商类进来,是不是就必须就该代码才能完成功能, 这样是不是违反了一个原则: 开闭原则。 而且工作也没完没了不是吗?
员工A好像理解了: 确实是这样, 那么我该怎么去做才能更好的满足变化呢?
经理微微一笑: 你可以了解一下命令模式。(命令模式将请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。 命令模式也支持可撤销的操作。)
2.3 学习命令模式
经理将一个简单的命令模式UML图发给了员工A:
并对他说,你好好看看这种图,并把它复现出来,接着体会一下这种方式的好处并与你之前的实现方式对比一下。
员工A发现,这种方式下的遥控器并不直接对厂商类进行操作,而是构造了一个Command接口,以依赖抽象的方式通过Commend的实现类来实现操作厂商类。这样的话,即使增加了厂商类,SimpleRemoteControl中的代码不用动,秩序新增一个Commend实现类,并修改客户端的使用配置即可。
2.4 使用命令模式实现需求
员工A收到命令模式的启发,决定重新设计。
两天后,他拿着新的设计找到了经理:
经理看完比较满意,但是随之给出了自己的疑问: 你打算怎么实现全局撤销功能呢? 我在上图看不到你对撤销功能的想法。
员工A摸了摸头尴尬的笑了笑: 我把这个给忘啦,给我一天时间,我考虑下这个该怎么去实现。
一天后,员工A带着如下设计去见经理:
在Command接口中新加一个undo方法,并在控制器中组合一个Command用来存储最新执行的命令对象,用来进行撤销操作。
经理满意的笑了。。
实现代码如下:
设计模式/src/main/java/OrderPattern/third/MainTest.java · 严家豆/设计模式 - 码云 - 开源中国 (gitee.com)
至此,命令模式的入门学习结束。
深入使用还需仔细观察一些开源项目或者自己所在项目里的使用,根据实际项目来进行理解,复现理解会更深刻。
命令模式将请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。 命令模式也支持可撤销的操作。