1、简介
命令模式(Command Pattern)是一种行为型设计模式,它使得能够将请求封装成对象,并且让我们可以用不同的请求对客户端进行参数化,同时能够支持请求排队或记录请求日志,以及支持可撤销的操作。
2、组成部分
命令模式的核心结构包括以下四个部分:
- Command(命令):声明了一个执行操作的接口。通常会包含一个 execute() 方法,当调用该方法时,接收者会执行相应的操作。
- ConcreteCommand(具体命令):将一个接收者对象与一个动作(如一个方法调用)绑定起来。实现 execute() 方法,调用接收者相应的操作。
- Receiver(接收者):知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。
- Invoker(调用者):请求者,发起者。通过一个或多个命令对象来执行请求。它要求命令对象执行请求。
这些组件间的关系如下图所示:
3、优缺点
优点:
- 降低系统的耦合度:命令模式将请求和接收者对象解耦,使得系统更加灵活,并且可以方便地添加或删除命令,而不会对其他的对象产生影响。
- 容易扩展新的命令:命令模式使得添加新的命令非常容易,只需要实现新的命令对象,并将其加入到命令的集合中即可。
- 支持撤销和重做操作:命令模式可以记录所有执行过的命令,支持撤销和重做操作,这对于某些需要撤销操作的场景非常有用。
- 支持请求队列:命令模式可以将请求放入请求队列中,使得请求可以被延迟执行或者排队执行,这可以避免请求的发送者和接收者之间的时间耦合。
缺点:
- 可能会导致过多的具体命令类:每个具体命令都需要实现一个新的类,这可能会导致系统中存在大量的具体命令类,从而增加系统的复杂度。
- 可能会增加系统的代码复杂度:命令模式中需要定义多个类和接口,从而增加了系统的代码复杂度,这可能会增加开发成本和维护成本。
总之,命令模式是一种非常有用的设计模式,它可以使得系统更加灵活,并且可以方便地添加或删除命令,同时也支持撤销和重做操作。但是,在使用命令模式时,需要根据具体的情况权衡其优缺点,从而选择最适合的设计模式。
4、使用场景
命令模式是一种常用的设计模式,它适用于以下场景:
- 需要对请求进行排队或记录日志:命令模式可以将请求放入请求队列中,从而可以对请求进行排队或记录日志。例如,我们可以将用户的操作请求放入队列中,以便在系统繁忙时处理这些请求。
- 需要支持撤销和重做操作:命令模式可以记录所有执行过的命令,支持撤销和重做操作。例如,我们可以使用命令模式来实现文本编辑器中的撤销和重做功能。
- 需要支持事务操作:命令模式可以将一组相关的操作封装在一起,以支持事务操作。例如,我们可以使用命令模式来实现数据库中的事务处理。
- 需要支持日志记录和审计功能:命令模式可以记录每个命令的执行时间和执行者等信息,从而支持日志记录和审计功能。例如,我们可以使用命令模式来记录用户对系统的操作行为。
- 需要支持命令的撤销和重做操作:命令模式可以记录每个命令的执行状态,以支持撤销和重做操作。例如,在图形用户界面中,我们可以使用命令模式来实现对绘制命令的撤销和重做操作。
- 需要实现多级菜单或工具栏:命令模式可以将菜单或工具栏中的每个按钮都封装成一个命令,从而可以方便地添加或删除按钮,而不会对其他的按钮产生影响。
总之,命令模式可以解耦请求发送者和请求接收者之间的关系,使得系统更加灵活,同时也支持撤销和重做操作,日志记录和审计等功能。因此,命令模式在许多场景下都是非常有用的。
5、代码实现
下面是一个简单的例子来演示命令模式的使用场景。假设我们有一个遥控器,它能够控制各种设备,包括电视、音响等等。现在我们要实现一个遥控器,它具有打开电视、关闭电视、打开音响、关闭音响等功能。
首先,我们需要定义一个命令接口,其中包含了一个 execute() 方法:
1. public interface Command { 2. void execute(); 3. }
然后,我们可以定义具体的命令类,如下所示:
1. public class TVOnCommand implements Command { 2. private TV tv; 3. 4. public TVOnCommand(TV tv) { 5. this.tv = tv; 6. } 7. 8. @Override 9. public void execute() { 10. tv.turnOn(); 11. } 12. }
1. public class TVOffCommand implements Command { 2. private TV tv; 3. 4. public TVOffCommand(TV tv) { 5. this.tv = tv; 6. } 7. 8. @Override 9. public void execute() { 10. tv.turnOff(); 11. } 12. }
1. public class StereoOnCommand implements Command { 2. private Stereo stereo; 3. 4. public StereoOnCommand(Stereo stereo) { 5. this.stereo = stereo; 6. } 7. 8. @Override 9. public void execute() { 10. stereo.turnOn(); 11. } 12. }
1. public class StereoOffCommand implements Command { 2. private Stereo stereo; 3. 4. public StereoOffCommand(Stereo stereo) { 5. this.stereo = stereo; 6. } 7. 8. @Override 9. public void execute() { 10. stereo.turnOff(); 11. } 12. }
其中,TVOnCommand、TVOffCommand、StereoOnCommand、StereoOffCommand 分别对应了打开电视、关闭电视、打开音响和关闭音响四种不同的命令。每个命令类都实现了 Command 接口,并且包含了一个接收者对象,用于执行实际的操作。
接下来,我们需要定义一个遥控器,它能够存储各种不同的命令,并且可以按下按钮来执行命令:
1. public class RemoteControl { 2. private Command[] onCommands; 3. private Command[] offCommands; 4. 5. public RemoteControl() { 6. onCommands = new Command[2]; 7. offCommands = new Command[2]; 8. } 9. 10. public void setCommand(int slot, Command onCommand, Command offCommand) { 11. onCommands[slot] = onCommand; 12. offCommands[slot] = offCommand; 13. } 14. 15. public void onButtonWasPushed(int slot) { 16. onCommands[slot].execute(); 17. } 18. 19. public void offButtonWasPushed(int slot) { 20. offCommands[slot].execute(); 21. } 22. }
在 RemoteControl 中,我们定义了两个数组,分别用于存储打开命令和关闭命令。setCommand() 方法用于设置命令,onButtonWasPushed() 和 offButtonWasPushed() 方法分别用于执行打开命令和关闭命令。
最后,我们需要定义接收者对象,也就是电视和音响:
1. public class TV { 2. public void turnOn() { 3. System.out.println("TV is turned on"); 4. } 5. 6. public void turnOff() { 7. System.out.println("TV is turned off"); 8. } 9. } 10. 11. public class Stereo { 12. public void turnOn() { 13. System.out.println("Stereo is turned on"); 14. } 15. 16. public void turnOff() { 17. System.out.println("Stereo is turned off"); 18. } 19. }
现在,我们可以使用这些类来实现一个简单的遥控器,如下所示:
1. public static void main(String[] args) { 2. RemoteControl remote = new RemoteControl(); 3. 4. TV tv = new TV(); 5. Stereo stereo = new Stereo(); 6. 7. remote.setCommand(0, new TVOnCommand(tv), new TVOffCommand(tv)); 8. remote.setCommand(1, new StereoOnCommand(stereo), new StereoOffCommand(stereo)); 9. 10. remote.onButtonWasPushed(0); // 打开电视 11. remote.offButtonWasPushed(0); // 关闭电视 12. remote.onButtonWasPushed(1); // 打开音响 13. remote.offButtonWasPushed(1); // 关闭音响 14. }
输出结果为:
TV is turned on
TV is turned off
Stereo is turned on
Stereo is turned off
这个例子中,我们使用了命令模式,将请求封装成了对象,并且支持了请求排队、记录请求日志和支持可撤销的操作。这使得我们可以将遥控器的功能动态地配置,并且可以将不同的命令对象组合成新的组合命令,从而实现更多的功能。同时,由于每个命令对象都独立,所以可以方便地进行单元测试和模块化开发。