设计模式
命令模式
在现实生活中,我们可以会遇到这种情况:在路边烧烤摊吃烧烤时,人少的时候还好,人多的时候就有可能出现师傅弄错顾客的口味【要辣/不要辣】或 忘记顾客是否已给钱 或者弄错烤串的数量等等一系列问题;而在烧烤店中却少有这种情况,在烧烤店中服务员通常会把顾客的需求记录下来,将记录交给厨师处理,顾客也不必直接和厨师打交道;这种行为模式就是命令模式的体现。还比如看电视时我们可以通过遥控来控制频道切换,音量的调节;通过空调遥控控制空调的温度、风向、模式等等。
在软件开发系统中,“方法的请求者” 与 “方法的实现者” 之间经常存在紧密的耦合关系,这样我们如果需要对方法进行 “撤销”、“排队”、“重做”,“记录日志” 等处理时就很不方便了;为了将 “方法的请求者” 与 “方法的实现者” 解耦,我们可以使用命令模式。
模式的定义
命令模式 (Command),将一个请求封装为一个对象,使 “行为的请求者” 与 “行为的实现者” 责任分割开;两者之间通过命令对象进行沟通,这样方便了对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式的优点:
- 它能较容易地设计一个命令队列。
- 在需要的情况下,可以较容易地将命令记入日志。
- 允许接收请求的一方决定是否要否决请求。
- 可以实现宏命令。命令模式可以与组合模式结合使用,将多个命令装配成一个组合命令,即宏命令。
- 可以容易地实现对请求的撤销和重做。
- 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
- 最关键的优点就是命令模式把 “行为的请求者” 与 “行为的实现者” 分割开,降低了对象间的耦合度。
缺点:
- 由于每一个具体操作都需要设计一个具体命令类,可能产生大量具体的命令类,这会增加系统的复杂性。
模式的结构
命令模式的主要角色如下:
- 抽象命令类角色(Command):声明了执行命令的接口,拥有执行命令抽象的方法。
- 具体命令类角色(Concrete Command):实现类抽象命令类,拥有命令接收者对象,并通过调用接收者的方法来完成命令需要执行的操作。
- 接收者角色(Receiver):执行命令功能相关的操作,是具体命令对象业务的真正执行者,包含具体的执行方法。
- 调用者角色(Invoker):作为请求的调用者,通常拥有很多命令对象,并通过命令对象来执行相关的请求,通过命令对象访问接收者。
其 UML 图如下:
模式的使用
这里我们以遥控器操作空调为例,演示命令模式的使用。
抽象命令类角色:
Command
提供了一个执行命令的方法execute()
。具体命令类角色:该实例中共有4个具体命令类,
PatternCommand
负责调节空调的模式【制冷、制热、抽湿、送风】,WindDirectionCommand
负责控制空调的风向【上下扫风、左右扫风】,TemperatureCommand
负责调节空调的温度【提高温度/降低温度】,TimingCommand
负责设置空调的定时开/关命令。接收者角色:空调类
AirConditioner
负责所有空调相关的操作【温度调节、风向调节、定时任务、模式设置】的具体实现。调用者角色:遥控类
RemoteControl
中持有多个具体命令,通过执行具体命令来实现客户端请求。
/**
* 接收者 - 空调类
*/
public class AirConditioner {
/**
* 最高温度
*/
private int maxTemperature = 36;
/**
* 最低温度
*/
private int minTemperature = 16;
/**
* 当前温度 - 默认26
*/
private int temperature = 26;
/**
* 风向 - 默认上-下扫风
*/
private String windDirection = "up_down";
/**
* 模式 - 默认制冷
*/
private String pattern = "refrigeration";
/**
* 定时开启
*/
private float timingOpen;
/**
* 定时关闭
*/
private float timingOff;
/**
* 修改空调温度
* @param offset
* @return
*/
public int updateTemperature(int offset) {
int result = this.temperature + offset;
if (result < minTemperature || result > maxTemperature) {
System.out.println("温度不在有效范围【16 - 36】");
return -1;
}
this.temperature = result;
System.out.println("修改温度成功,现在空调温度为:" + this.temperature);
return temperature;
}
/**
* 修改空调风向
* @param windDirection
* @return
*/
public String updateWindDirection(String windDirection) {
this.windDirection = windDirection;
System.out.println("修改风向成功,现在空调风向为:" + windDirection);
return windDirection;
}
/**
* 设置定时开
* @param time
* @return
*/
public boolean setTimingOpen(float time) {
if (time <= 0 || time > 24) {
System.out.println("定时时间不在有效范围【> 0 且 <= 24】");
return false;
}
this.timingOpen = time;
System.out.println("设置空调定时开启成功,时间为 " + time + " 小时后开启");
return true;
}
/**
* 设置定时关
* @param time
* @return
*/
public boolean setTimingOff(float time) {
if (time <= 0 || time > 24) {
System.out.println("定时时间不在有效范围【> 0 且 <= 24】");
return false;
}
this.timingOpen = time;
System.out.println("设置空调定时关闭成功,时间为 " + time + " 小时后关闭");
return true;
}
/**
* 修改空调模式
* @param pattern
* @return
*/
public boolean updatePattern(String pattern) {
this.pattern = pattern;
System.out.println("修改空调模式成功,当前模式为:" + pattern);
return true;
}
}
/**
* 抽象命令类
*/
public interface Command {
/**
* 执行命令
*/
void execute();
}
/**
* 具体命令类 - 空调模式调节命令
*/
public class PatternCommand implements Command{
/**
* 空调对象
*/
private AirConditioner airConditioner;
/**
* 空调模式
*/
private String pattern;
public PatternCommand(AirConditioner airConditioner, String pattern) {
this.airConditioner = airConditioner;
this.pattern = pattern;
}
@Override
public void execute() {
airConditioner.updatePattern(pattern);
}
}
/**
* 具体命令类 - 空调风向调节命令
*/
public class WindDirectionCommand implements Command{
/**
* 空调对象
*/
private AirConditioner airConditioner;
/**
* 风向
*/
private String windDirection;
public WindDirectionCommand(AirConditioner airConditioner, String windDirection) {
this.airConditioner = airConditioner;
this.windDirection = windDirection;
}
@Override
public void execute() {
airConditioner.updateWindDirection(windDirection);
}
}
/**
* 具体命令类 - 空调温度调节命令
*/
public class TemperatureCommand implements Command{
/**
* 空调对象
*/
private AirConditioner airConditioner;
/**
* 温度偏移量,正数为加,负数为减
*/
private int offset;
public TemperatureCommand(AirConditioner airConditioner, int offset) {
this.airConditioner = airConditioner;
this.offset = offset;
}
@Override
public void execute() {
airConditioner.updateTemperature(offset);
}
}
/**
* 具体命令类 - 空调定时命令
*/
public class TimingCommand implements Command{
/**
* 空调对象
*/
private AirConditioner airConditioner;
/**
* 类型:开启[open]/关闭[off]
*/
private String type;
/**
* 时间
*/
private float time;
public TimingCommand(AirConditioner airConditioner, String type, float time) {
this.airConditioner = airConditioner;
this.type = type;
this.time = time;
}
@Override
public void execute() {
if ("off".equals(type)) {
airConditioner.setTimingOff(time);
} else {
airConditioner.setTimingOpen(time);
}
}
}
/**
* 调用者 - 遥控类
*/
public class RemoteControl {
private List<Command> commands = new CopyOnWriteArrayList<>();
/**
* 添加命令
* @param command
*/
public void addCommand(Command command) {
commands.add(command);
}
/**
* 删除命令
* @param command
*/
public void removeCommand(Command command) {
commands.remove(command);
}
/**
* 全部执行
*/
public void execute() {
for (Command command : commands) {
command.execute();
}
}
}
/**
* 命令模式测试类
*/
public class CommandTest {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
// 温度 21,风向 上-下扫风,模式:制冷, 1小时候定时关闭空调
Command temperatureCommand = new TemperatureCommand(airConditioner, -5);
Command windDirectionCommand = new WindDirectionCommand(airConditioner, "up_down");
PatternCommand patternCommand = new PatternCommand(airConditioner, "refrigeration");
TimingCommand timingCommand = new TimingCommand(airConditioner, "off", 1);
RemoteControl remoteControl = new RemoteControl();
remoteControl.addCommand(temperatureCommand);
remoteControl.addCommand(windDirectionCommand);
remoteControl.addCommand(patternCommand);
remoteControl.addCommand(timingCommand);
//执行命令
remoteControl.execute();
}
}
运行程序,结果如下:
小总结
- 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开;调用者发出命令,接收者执行命令。
- 每一个命令都是一个操作:调用者发出请求,要求执行一个操作;接收者收到请求,并执行操作。
- 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
- 命令模式将调用者和接收者独立开来,使得调用者不必知道接收者的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
- 命令模式的关键在于引入了抽象命令接口,调用者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
模式的应用场景
当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。
- 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
- 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以与 “备忘录模式” 配合使用,将命令对象存储起来,以备后续恢复。