命令模式的本質(zhì)是對(duì)命令進(jìn)行封裝,將發(fā)出命令的責(zé)任和執(zhí)行命令的責(zé)任分割開扎运。關(guān)鍵在于引入了抽象命令接口沼侣,且發(fā)送者針對(duì)抽象命令接口編程,只有實(shí)現(xiàn)了抽象命令接口的具體命令才能與接收者相關(guān)聯(lián)赁项。
-
UML:
image.png - 模型:家電自動(dòng)遙控器
- 特點(diǎn):我們想用一個(gè)遙控器來(lái)控制多個(gè)家電,例如澈段,電燈悠菜,音響,空調(diào)败富,電視等悔醋。家電是請(qǐng)求的接收者,遙控器是請(qǐng)求的發(fā)送者兽叮,遙控器上有一些按鈕芬骄,不同的按鈕對(duì)應(yīng)家電的不同操作。抽象命令角色由一個(gè)命令接口來(lái)扮演鹦聪,有三個(gè)具體的命令類實(shí)現(xiàn)了抽象命令接口账阻,這三個(gè)具體命令類分別代表三種操作:打開家電、關(guān)閉關(guān)閉和啥都不干泽本。
public class Light {
//燈的位置淘太,臥室,廚房...
String loc = "";
public Light(String loc) {
this.loc = loc;
}
public void On() {
System.out.println(loc + " On");
}
public void Off() {
System.out.println(loc + " Off");
}
}
//音響設(shè)備
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");
}
}
//模擬設(shè)備廠家提供的遙控器控制接口
public interface Control {
public void onButton(int slot);
public void offButton(int slot);
public void undoButton();
}
使用傳統(tǒng)的面向?qū)ο蟮脑O(shè)計(jì)规丽,一般是創(chuàng)建一個(gè)遙控器實(shí)例蒲牧,將燈、音響的實(shí)例傳給遙控器嘁捷,然后根據(jù)遙控器的按鈕設(shè)置不同的家電操作造成,例如按下1號(hào)鍵是音響開显熏,2號(hào)鍵是音響加音量等
public class TraditionControl implements Control {
Light light;
Stereo stereo;
public TraditionControl(Light light, Stereo stereo) {
this.light = light;
this.stereo = stereo;
}
@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() {
}
}
這樣設(shè)計(jì)雄嚣,如果要加一種設(shè)備,這種設(shè)計(jì)的控制器就不好添加了,首先要在控制器中注入設(shè)備缓升,按鍵處理中要再加新設(shè)備的處理鼓鲁。所以,這樣就高耦合了港谊,不利于擴(kuò)展骇吭。
- 使用命令模式:
//將命令抽象處理請(qǐng)求接口
public interface Command {
public void execute();
public void undo();
}
//臥室關(guān)燈命令
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();
}
}
//臥室開燈命令
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();
}
}
//操作無(wú)響應(yīng)命令
public class NoCommand implements Command {
@Override
public void execute() {}
@Override
public void undo() {}
}
//音響開命令
public class StereoOnCommand implements Command {
private Stereo setreo;
public StereoOnCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
setreo.On();
setreo.SetCd();
}
@Override
public void undo() {
setreo.Off();
}
}
//音響關(guān)命令
public class StereoOffCommand implements Command {
private Stereo setreo;
public StereoOffCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
setreo.Off();
}
@Override
public void undo() {
setreo.On();
setreo.SetCd();
}
}
//音響加音量
public class StereoAddVolCommand implements Command{
private Stereo setreo;
public StereoAddVolCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
int vol= setreo.GetVol();
if(vol<11){
setreo.SetVol(++vol);
}
}
@Override
public void undo() {
int vol= setreo.GetVol();
if(vol>0){
setreo.SetVol(--vol);
}
}
}
//音響減音量命令
public class StereoSubVolCommand implements Command{
private Stereo setreo;
public StereoSubVolCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
int vol= setreo.GetVol();
if(vol>0){
setreo.SetVol(--vol);
}
}
@Override
public void undo() {
int vol= setreo.GetVol();
if(vol<11){
setreo.SetVol(++vol);
}
}
}
//當(dāng)有了這樣命令之后,我們只需要將命令設(shè)置給遙控器的不同的按鍵即可歧寺,用戶在按鍵時(shí)會(huì)請(qǐng)求執(zhí)行已經(jīng)設(shè)定好的命令燥狰,由命令再調(diào)用具體家電執(zhí)行。
public class CommandModeControl implements Control{
//所有開命令
private Command[] onCommands;
//所有關(guān)命令
private Command[] offCommands;
//記錄上一個(gè)執(zhí)行的命令
private Stack<Command> stack=new Stack<Command>();
public CommandModeControl(){
onCommands=new Command[5];
offCommands=new Command[5];
Command noCommand=new NoCommand();
// 初始化為無(wú)操作命令
for(int i=0,len=onCommands.length;i<len;i++) {
onCommands[i]=noCommand;
offCommands[i]=noCommand;
}
}
//設(shè)置具體按鍵命令
public void setCommand(int slot,Command onCommand,Command offCommand){
onCommands[slot]=onCommand;
offCommands[slot]=offCommand;
}
@Override
public void onButton(int slot) {
//執(zhí)行命令
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();
}
}
//使用遙控器
public class ControlTest {
public static void main(String[] args) {
//創(chuàng)建遙控器斜筐,也就是UML中的Invoker實(shí)現(xiàn)類
CommandModeControl control = new CommandModeControl();
Light bedroomlight = new Light("BedRoom");
Light kitchlight = new Light("Kitch");
Stereo stereo = new Stereo();
//設(shè)置各種命令
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};
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);
//操作遙控器就能執(zhí)行已經(jīng)綁定好的命令控制家電了
control.onButton(0);
control.undoButton();
//control.offButton(0);
control.onButton(1);
control.offButton(1);
control.onButton(2);
control.onButton(3);
control.offButton(3);
control.undoButton();
control.offButton(2);
control.undoButton();
}
}
通過上面的設(shè)計(jì)龙致,遙控器不需要注入家電設(shè)備,而是注入各種命令顷链。如果新加一種家電目代,則只需要擴(kuò)展新的命令,并注入到遙控器中即可嗤练。遙控器不關(guān)心家電的實(shí)現(xiàn)榛了,它是調(diào)用命令接口,解耦了遙控器與家電煞抬。但是可能會(huì)導(dǎo)致某些系統(tǒng)有過多的具體命令類霜大。因?yàn)獒槍?duì)每一個(gè)命令都需要設(shè)計(jì)一個(gè)具體命令類,因此某些系統(tǒng)可能需要大量具體命令類此疹。