您的位置:首页 > 编程语言 > Java开发

Java设计模式-命令模式(Head First 设计模式5)

2016-02-26 13:51 399 查看

一、命令模式定义

来看看命令模式的定义:

命令模式:将请求封装成对象,以便使用不同的请求、日志、队列等来参数化其他对象。命令模式也支持撤销操作。

每次讲一个模式时,从定义都不能体会其中的技巧,所以接着通过举例子来说明命令模式。

二、命令模式的举例

下面来看看多用遥控器是如何使用命令模式的。

2.1需求

假设某个公司需要设计一个多用功能的遥控器。基本的需求如下:

该遥控器有可以控制风扇,白炽灯,热水器等等的多对开关,而且可能还有其他的电器,暂时不做其功能,但是希望可以保留接口,用的时间可以方便的扩展。

除上面的需求之外,还需要有个按钮,可以撤销上一步的操作。基本功能如下图:



2.2问题

在设计遥控器时,风扇,白炽灯,热水器的开关方法已经定义好,其名字各不相同。不妨设置其方法为如下:



由于各种电器的开关方法都不一样,而且还存在一个待扩展的电器,如果没有学习命令模式之前,我们在设置扩展的开关时,会出现的问题是什么呢?假设现在有电视,冰箱还可能会用到遥控器,那么我们会在最后一个开关上写if else,当然如果哪一天有多了一个大门也加入了我们的遥控的行列,这样我们继续加if else ,很显然随着电器的高速发展,会有多个需要遥控可以控制的。

而且这样做的话,控制电视、冰箱等物品的代码(如LightOn、HeaterOn等)会写在遥控器中,增加新的被控制物品时,不可避免的导致会修改遥控器代码,这样做也不符合开闭原则的要求。

举个例子,如果我们是需要遥控的客户,现在有一款遥控如果有遥控可以进行扩展,一种是可以扩展指定类型的,像上面的,只能再去扩展电视和冰箱中的一种,偶尔有一天你看到隔壁邻居的门,也可以使用遥控了,所以你去把你的高级遥控器,拿到扩展店时,扩展工程师说了,现在只能扩展电视和冰箱,不支持对大门的遥控扩展.

我们肯定是希望,可以自由的扩展,大门可以使用遥控了,就对大门扩展,车门使用遥控了,就对车门扩展……其实也就是一种松耦合的实现。

2.3解决问题

命令模式的核心就在于命令对象,通过命令对象,将命令的发出者和最终的执行者解耦。就像在餐馆点餐时,顾客并不直接向厨师提出要求,而是借助点餐单,顾客将点餐的命令封装在点餐单上,点餐单再要求厨师做菜。即客户控制点餐单,点餐单控制厨师,客户和厨师之间解耦。

而命令对象也起到这样的“桥梁”的作用,命令对象想要控制哪个类,就在构造函数中传入相应类的对象。

实现命令接口:

//实现命令接口,所有的命令对象实现相同的包含一个方法的接口
public interface Command {
public void execute();
}


实现一个命令:

(需要建立起来“命令”与“被控制者(电灯)”的联系)

public class LightOnCommand implements Command {
Light light;

//在构造函数传入某个被控制对象,以便让这个命令控制
public LightOnCommand(Light light) {
this.light = light;
}

//由命令对象控制最终执行者,命令对象提供统一的execute方法,而封装了最终执行者的方法
//类似上文提到的“菜单控制厨师”
public void excute() {
light.on();
}
}


控制器使用命令对象:

(需要建立起来“控制者(遥控器)”与“命令”的联系)

public class SimpleRemoteControl {
Command command;

//可以通过构造函数传入一个命令对象,也可以通过其他方法,这里通过自定义的setCommand()方法
public SimpleRemoteControl() {}
public void setCommand(Command command) {
this.command = command;
}

public void buttonPressed() {
command.execute();
}
}


回到我们的遥控器问题上面来,我们可以先定义好我们的风扇,白炽灯,热水器。然后定义其分别的开关命令,每个命令都有自己对应的电器引用,而且会在命令的execute中包装电器的开或者关,最后需要把命令安装到遥控器上面,在遥控器上每个按钮都对应有自己的激发方法,用代码表示如下:

public class RemoteControlTest {
public static void main(String[] args) {

SimpleRemoteControl control = new SimpleRemoteControl();//建立遥控器对象
Light light = new Light();//建立灯对象
LightOnCommand lightOn = new LightOnCommand(light);//建立命令对象,同时将被控制的灯作为参数传入,建立命令对象和灯的关联

control.setCommand(lightOn);//建立遥控器的命令对象的关联
control.buttonPressed();
}
}


这样基本上就实现了我们的现有的电器的遥控。需要注意的是,在开始初始化遥控器时,对每个命令初始化成了NoCommand,也就是什么都不执行。在命令的初始化经常使用,同时这也解决了我们的在扩展前什么都不做的难题。看了风扇,白炽灯,热水器的遥控实现,进一步的扩展任何的电器,相信都不是什么难事。但是还有个功能没有实现,就是撤销到上一步的操作,接下来我们就来实现撤销操作。

2.4问题的补充说明

撤销操作就想我们遥控中的返回一样。基本上就是灯亮着,突然按了一下关灯,然后再按一下返回键,灯就亮了。其他的电器同样的道理。下面先看一下灯的撤销原理,命令除了执行外还有一个撤销,所以我们需要先都命令的接口添加一个方法。

/// <summary>
/// 命令接口
/// </summary>
public interface ICommand
{
void Excute();
void Undo();
}


对于开灯需要做的修改如下:

public class LightOnCommand : ICommand
{
Light light;
public LightOnCommand(Light light)
{
this.light = light;
}
public void Excute()
{
light.LightOn();
}
/// <summary>
/// 调用命令的反命令
/// </summary>
public void Undo()
{
light.LightOff();
}
}


其他命令同理,代码会在源码中一并给出。也就是每个命令都有自己的反命令,在Undo方法里面也就是调用反命令的Excute方法。每当按下一个按钮时,就去记录其命令的名称,如果按撤销的话,就执行命名的Undo方法。下面给出主要代码:

public void OnButtonWasPressed(int slot)
{
onCommands[slot].Excute();
backCommand=onCommands[slot];
}
public void OffButtonWasPressed(int slot)
{
offCommands[slot].Excute();
backCommand = offCommands[slot];
}
public void BackButtonWasPressed()
{
backCommand.Undo();
}


以上是对遥控器对命令的撤销,需要注意两点1、通过记住命令执行之前的状态,然后去恢复到原来的状态。2、在每次执行之后要记住执行的那个命令。也即记住命令和记住状态。

除了一次执行一个命令和撤销一个命令,当然还可以一次执行多个命令。下面给出主要代码:

public class MutlipleCommand : ICommand
{
ICommand[] commands;
ICommand[] backCommands;
public MutlipleCommand(ICommand[] commands)
{
this.commands = commands;
backCommands = new ICommand[commands.Length];
}

public void Excute()
{
for (int i = 0; i < commands.Length; i++)
{
commands[i].Excute();
backCommands[i] = commands[i];
}

}

public void Undo()
{
for (int i = 0; i < commands.Length; i++)
{
backCommands[i].Undo();
}
}
}


三、命令模式类图



四、总结

命令模式主要通过中介Command实现了发出命令者和命令的执行者,也即Invoke类和Receiver的松耦合。本文先给出了命令模式的定义,通过吃饭的例子给出了使用命令模式实现遥控器设计思路,最后还提到了撤销命令和一个命令实现多个命令的做法。

参考文章:/article/4975944.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: