一.命令模式原理
1.家电自动化遥控器API项目的问题
假设有一家家电自动化的公司,在智能家庭、智能家居里每样电器都是可以互相联通的,都是可以通过一个统一的终端来控制,这项目模拟的例子就是遥控器,可以控制家里所有的家电。这个遥控器简化以后就放成这样五排按钮,每排理解为一个为关、一个为开(或者是音响的话,一个是把声音调大,一个是把声音调小,是电视机的话,一个是加频道,一个是减频道)。
项目要做的是通过在遥控器上加载好我们设计好的软件或项目,通过这上面的键可以控制电灯、电视、音响等家电,项目的要求是各种家电的API给你后,要可以灵活的把它加上去,控制家电的功能。
家电公司提供的API可以认为是SDK、API接口,我们获取到这个接口以后,去控制电灯的开还是关、空调的加温度还是减温度;遥控器本身也应该是一个接口或模板,因为按照这个模板做change按钮,就会调用下面提供的函数或者方法,在这里就是我们要设计的自动化遥控器API,change按钮调用遥控器API以后去控制家电API去控制家电。
Light.java
package com.bijian.study.device; /** * 可以认为是各种家电公司提供的不同SDK或类对象 * 灯类 * @author bijian * */ public class Light { String loc = ""; //loc表示是哪个灯,是客厅的还是卧室的还是厨房的 public Light(String loc) { this.loc = loc; } //开灯 public void On() { System.out.println(loc + " On"); } //关灯 public void Off() { System.out.println(loc + " Off"); } }
Stereo.java
package com.bijian.study.device; /** * CD类 * @author bijian * */ public class Stereo { static int volume = 0; public void On() { System.out.println("Stereo On"); } public void Off() { System.out.println("Stereo Off"); } public void SetCd() { System.out.println("Stereo SetCd"); } //设置音量 public void SetVol(int vol) { volume = vol; System.out.println("Stereo volume=" + volume); } //获得音量值 public int GetVol() { return volume; } //开始 public void Start() { System.out.println("Stereo Start"); } }
Control.java
package com.bijian.study.oo; /** * 控制器接口 * @author bijian */ public interface Control { public void onButton(int slot); public void offButton(int slot); public void undoButton(); }
TraditionControl.java
package com.bijian.study.oo; import com.bijian.study.device.Light; import com.bijian.study.device.Stereo; /** * 传统的作法是把灯的对象或音响的对象直接传到遥控器上,遥控器change时根据灯和音响放到哪个插槽上on或off时调用相应的功能 * @author bijian */ public class TraditionControl implements Control { Light light; Stereo stereo; //构造函数中就传入灯和音响 public TraditionControl(Light light, Stereo stereo) { this.light = light; this.stereo = stereo; } //在on时我们假设第一个插槽控制的是灯,第二、三个插槽是对音响的控制 @Override public void onButton(int slot) { // TODO Auto-generated method stub switch (slot) { case 0: light.On();//灯亮 break; case 1: stereo.On();//音响打开 break; case 2: int vol = stereo.GetVol(); if (vol < 11) { stereo.SetVol(++vol);//音响音量调高 } break; } } @Override public void offButton(int slot) { // TODO Auto-generated method stub switch (slot) { case 0: light.Off();//灯关 break; case 1: stereo.Off();//音响关掉 break; case 2: int vol = stereo.GetVol(); if (vol > 0) { stereo.SetVol(--vol);//音响音量调低 } break; } } @Override public void undoButton() { // TODO Auto-generated method stub } }
ControlTest.java
package com.bijian.study.oo; import com.bijian.study.device.Light; import com.bijian.study.device.Stereo; /** * 主程序 * @author bijian */ public class ControlTest { public static void main(String[] args) { Control ctl; Light light = new Light("Bedroom");//卧室的灯 Stereo stereo = new Stereo(); ctl = new TraditionControl(light, stereo); ctl.onButton(0);//把灯打开 ctl.offButton(0);//把灯关掉 ctl.onButton(1);//把音响打开 ctl.onButton(2);//把音量调大 ctl.offButton(2);//把音量调小 ctl.offButton(1);//把音响关掉 } }
运行结果:
Bedroom On Bedroom Off Stereo On Stereo volume=1 Stereo volume=0 Stereo Off
但项目对自动化遥控器要求扩展性好、维护性好。假设有新的设备进来,要加载新的东西,要能加进来且遥控器本身的变化要小,出现故障的概率要小,扩展过程中不能带入BUG。也就是要对功能扩展开放,对代码修改关闭。
但上述的代码,要加新的设备,必须在TraditionControl.java中增加本地变地,构造函数里也要加进来,同时控制的onButton、offButton也要加case分支及逻辑。这种设计对功能开放,但对代码的修改也是开放的,相悖对功能扩展开放,对代码修改关闭原则。这种设计的问题也可简单理解为耦合度高,即遥控器具体使用或调用过程中的功能是设备里面具体的执行函数,也就是说控制器和设备之间是强相关的,所以有一方变动,另一方也要变动。
2.命令模式的原理
命令模式就是把原来的命令抽取成一个对象,然后把这个对象放到我们的控制器里面。如下我们把所有命令抽象成接口,接口里有execute、undo两个方法,我们把所有对原子的执行都封装成命令对象。在控制器Invoker中有一个setCommand,把这里所有的具体的命令放到setCommand里,把它和插槽关联起来,在控制器里面,插槽的调用只是调用接口,因为它只是接口,和具体设备不相关了,就减耦合了,具体执行这个命令是实现什么功能由具体设备决定的。
命令模式:将请求、命令、动作等封装成对象,这样可以让项目使用这些对象来参数化其他对象。使得命令的请求者和执行者解耦。
3.设计方案类图
setCommand函数就是把命令接口和具体的插槽里面的按钮关联起来,而具体命令接口实现和扩展以后,我们把所有家电的执行函数都包装成对象。比如电灯的开是一个命令对象LightOnCommand,音响的开也是一个命令对象StereoOnCommand。在遥控器上按下一个按钮以后就会调用命令的执行函数,执行函数具体化以后就是不同的命令,不同的命令再去调相应的物理设备,执行相应的开、关等等。这样就保证了遥控器上面的代码在调用具体实现时和原来的设备就解耦了。如增加设备,只要增加命令并将其通过setCommand方法设置到控制器上即可。
二.命令模式项目代码
把灯的打开、关闭抽取为两个命令类,分别是LightOnCommand、LightOffCommand,同理音响也是一样,拆分成StereoOnCommand、StereoOffCommand、StereoAddVolCommand、StereoSubVolCommand。
LightOffCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; import com.bijian.study.device.Light; public class LightOffCommand implements Command { private Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.Off(); } @Override public void undo() { light.On(); } }
LightOnCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; import com.bijian.study.device.Light; public class LightOnCommand implements Command { private Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.On(); } @Override public void undo() { light.Off(); } }
StereoAddVolCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; import com.bijian.study.device.Stereo; public class StereoAddVolCommand implements Command { private Stereo stereo; public StereoAddVolCommand(Stereo stereo) { this.stereo = stereo; } @Override public void execute() { int vol = stereo.GetVol(); if (vol < 11) { stereo.SetVol(++vol);//音响音量调高 } } @Override public void undo() { int vol = stereo.GetVol(); if (vol > 0) { stereo.SetVol(--vol);//音响音量调低 } } }
StereoSubVolCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; import com.bijian.study.device.Stereo; public class StereoSubVolCommand implements Command { private Stereo stereo; public StereoSubVolCommand(Stereo stereo) { this.stereo = stereo; } @Override public void execute() { int vol = stereo.GetVol(); if (vol > 0) { stereo.SetVol(--vol);//音响音量调低 } } @Override public void undo() { int vol = stereo.GetVol(); if (vol < 11) { stereo.SetVol(++vol);//音响音量调高 } } }
StereoOnCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; import com.bijian.study.device.Stereo; public class StereoOnCommand implements Command { private Stereo stereo; public StereoOnCommand(Stereo stereo) { this.stereo = stereo; } @Override public void execute() { stereo.On(); stereo.SetCd(); } @Override public void undo() { stereo.Off(); } }
StereoOffCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; import com.bijian.study.device.Stereo; public class StereoOffCommand implements Command { private Stereo stereo; public StereoOffCommand(Stereo stereo) { this.stereo = stereo; } @Override public void execute() { stereo.Off(); } @Override public void undo() { stereo.On(); stereo.SetCd(); } }
NoCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; public class NoCommand implements Command { @Override public void execute() { } @Override public void undo() { } }
MarcoCommand.java
package com.bijian.study.command.impl; import com.bijian.study.command.Command; /** * 宏命令 * @author bijian * */ public class MarcoCommand implements Command { private Command[] commands; public MarcoCommand(Command[] commands) { this.commands = commands; } @Override public void execute() { for(int i=0,len=commands.length;i<len;i++) { commands[i].execute(); } } @Override public void undo() { for (int i = commands.length - 1; i >= 0; i--) { commands[i].undo(); } } }
Command.java
package com.bijian.study.command; public interface Command { public void execute(); public void undo(); }
CommandModeControl.java
package com.bijian.study.command; import java.util.Stack; import com.bijian.study.command.impl.NoCommand; import com.bijian.study.oo.Control; /** * 命令模式实现的摇控器 */ public class CommandModeControl implements Control { //on命令数组 private Command[] onCommands; //off命令数组 private Command[] offCommands; private Stack<Command> stack = new Stack<Command>(); public CommandModeControl() { onCommands = new Command[5]; offCommands = new Command[5]; Command noCommand = new NoCommand(); //onButton、offButton方法中无需判断onCommands[slot]是否为空 for(int i=0,len = onCommands.length;i<len;i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } @Override public void onButton(int slot) { onCommands[slot].execute(); stack.push(onCommands[slot]); } @Override public void offButton(int slot) { offCommands[slot].execute(); stack.push(offCommands[slot]); } @Override public void undoButton() { stack.pop().undo(); } }
ControlTest.java
package com.bijian.study.command; import com.bijian.study.command.impl.LightOffCommand; import com.bijian.study.command.impl.LightOnCommand; import com.bijian.study.command.impl.MarcoCommand; import com.bijian.study.command.impl.StereoAddVolCommand; import com.bijian.study.command.impl.StereoOffCommand; import com.bijian.study.command.impl.StereoOnCommand; import com.bijian.study.command.impl.StereoSubVolCommand; import com.bijian.study.device.Light; import com.bijian.study.device.Stereo; public class ControlTest { public static void main(String[] args) { CommandModeControl control = new CommandModeControl(); MarcoCommand onmarco,offmarco; Light bedroomlight = new Light("BedRoom"); Light kitchlight = new Light("Kitch"); Stereo stereo = new Stereo(); LightOnCommand bedroomlighton = new LightOnCommand(bedroomlight); LightOffCommand bedroomlightoff = new LightOffCommand(bedroomlight); LightOnCommand kitchlighton = new LightOnCommand(kitchlight); LightOffCommand kitchlightoff = new LightOffCommand(kitchlight); Command[] oncommands = {bedroomlighton, kitchlighton}; Command[] offcommands = {bedroomlightoff, kitchlightoff}; onmarco = new MarcoCommand(oncommands); offmarco = new MarcoCommand(offcommands); StereoOnCommand stereoOn = new StereoOnCommand(stereo); StereoOffCommand stereoOff = new StereoOffCommand(stereo); StereoAddVolCommand stereoaddvol = new StereoAddVolCommand(stereo); StereoSubVolCommand stereosubvol = new StereoSubVolCommand(stereo); control.setCommand(0, bedroomlighton, bedroomlightoff); control.setCommand(1, kitchlighton, kitchlightoff); control.setCommand(2, stereoOn, stereoOff); control.setCommand(3, stereoaddvol, stereosubvol); control.setCommand(4, onmarco, offmarco); //不带回退功能的应用 System.out.println("------------不带回退功能的应用-------------"); control.onButton(0); control.offButton(0); control.onButton(1); control.offButton(1); control.onButton(2); control.onButton(3); control.offButton(3); control.offButton(2); //带回退功能的应用 System.out.println("------------带回退功能的应用-------------"); control.onButton(0); control.undoButton(); control.onButton(1); control.offButton(1); control.onButton(2); control.onButton(3); control.offButton(3); control.undoButton(); control.offButton(2); control.undoButton(); //宏命令应用 System.out.println("------------宏命令应用-------------"); control.onButton(4); control.offButton(4); control.onButton(4); control.undoButton(); } }
运行结果:
------------不带回退功能的应用------------- BedRoom On BedRoom Off Kitch On Kitch Off Stereo On Stereo SetCd Stereo volume=1 Stereo volume=0 Stereo Off ------------带回退功能的应用------------- BedRoom On BedRoom Off Kitch On Kitch Off Stereo On Stereo SetCd Stereo volume=1 Stereo volume=0 Stereo volume=1 Stereo Off Stereo On Stereo SetCd ------------宏命令应用------------- BedRoom On Kitch On BedRoom Off Kitch Off BedRoom On Kitch On Kitch Off BedRoom Off
三.命令模式关键点
1.命令模式的意义
传统设计耦合在一起,不易扩展、升级、维护,同时也违背了对功能扩展开放,对代码修改关闭原则。命令模式把外围设备、API或命令封装组合成一个对象,具体某个按钮按下去的时候,调的是这个抽象的命令接口,这个抽象的命令接口在运行时会赋值一个具体的命令对象,摇控器按钮并不知道具体执行的是哪个对象或设备,因为运行时是通过setCommand把具体的对象或具体的命令执行放到按钮下面,按钮只知道调用一个接口的execute方法,这样就达到解耦的目的。
2.解耦的意义
摇控器的升级和外围设备不相干了,同样外围设备的升级和摇控器也不相干了。层次结构清晰,扩展、升级、维护相当容易,而且不易引入新的BUG。
相关推荐
Java设计模式之命令模式/Java函数式编程 笔记
Java设计模式是面向对象编程领域中的重要概念,它是一套被广泛接受并实践的解决软件设计问题的经验总结。设计模式并非具体的代码或库,而是一种在特定情境下为了解决常见问题而制定的通用解决方案的描述。它们描述了...
《Java设计模式之禅》是一本深入浅出讲解设计模式的书籍,书中不仅包含23种经典设计模式的案例,还详细介绍了设计模式背后的思想和原则,适合初学者以及对设计模式有一定了解的程序员阅读。本书旨在帮助读者理解如何...
《Java设计模式》是刘伟老师的一本经典教材,它深入浅出地讲解了软件设计中的重要概念——设计模式。设计模式是经验丰富的开发者在解决常见问题时总结出的通用解决方案,是软件开发中的智慧结晶。这本书的课后习题和...
Java设计模式是软件工程中的一种最佳实践,它总结了在特定场景下解决常见问题的经验,为程序员提供了可重用的解决方案。本资料“《java设计模式》课后习题模拟试题解答——刘伟.zip”主要涵盖了Java设计模式的学习与...
### Java设计模式详解 在软件开发领域,设计模式是一种被广泛采用的解决方案,用来解决常见的设计问题。设计模式不仅能够帮助开发者写出可复用、可维护的代码,还能提高团队间的沟通效率。以下是对给定文件中提到的...
目录: 前 言 第一部分 大旗不挥,谁敢冲锋——热身篇 第1章 单一职责原则 1.1 我是“牛”类,我可以担任多职吗 1.2 绝杀技,打破你的传统思维 1.3 我单纯,所以我快乐 1.4 最佳实践 ...附录:23个设计模式
Java设计模式是软件工程中的一种最佳实践,它提供了一套标准的解决方案,用于解决在编写可维护、可扩展和高效代码时经常遇到的问题。这些模式是经验丰富的开发者们在面对相似问题时,经过反复试验和优化后总结出的...
《Java设计模式》是刘伟教授的一本关于设计模式的教材,主要面向高等学校的学生和对Java编程有深入兴趣的开发者。设计模式是软件工程中的一种重要思想,它封装了在特定场景下的问题解决方案,可以提高代码的可读性、...
Java作为一门广泛应用的开发语言,其设计模式的应用对于提高代码质量、可维护性和可扩展性至关重要。本文将着重探讨创建型模式、结构型模式和行为模式这三大类设计模式,并结合六项设计原则进行深入解析。 首先,...
" JAVA 设计模式概述" JAVA 设计模式是指在软件设计过程中,为了提高代码的可维护性、灵活性和可扩展性所使用的一些惯用解决方案。JAVA 设计模式可以分为三种:创建模式、结构模式和行为模式。 1. 创建模式 创建...
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段...
### Java设计模式的应用 #### 一、引言 在当今快速发展的软件开发领域,Java作为一门功能强大且灵活的语言,不仅拥有丰富的API资源,还能与强大的数据库系统无缝对接。这使得许多开发人员能够以模块化的形式构建...
这是JAVA设计模式中属于行为模式的部分,包括Template(模板模式)、Chain of Responsibility(责任链模式)、Memento(纪念品模式)、Mediator(中介模式)、Strategy(策略模式)、State 、Observer(观察者模式)、Visitor...
命令模式是一种设计模式,它将请求封装为一个对象,使得我们可以使用不同的请求、队列请求、或者支持可撤销的操作。在Java中,命令模式被广泛应用于各种场景,尤其是在需要解耦调用者与具体实现之间关系的时候。接...
【Java设计模式】《设计模式之禅》中的23种设计模式.zip 【Java设计模式】《设计模式之禅》中的23种设计模式.zip 【Java设计模式】《设计模式之禅》中的23种设计模式.zip 【Java设计模式】《设计模式之禅》中的23种...