设计模式 命令模式(Command Pattern)
命令模式简绍
命令模式(Command Pattern)是一种行为设计模式,它把请求封装成对象,从而让你可以用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
组成部分:
- 命令接口(Command Interface):定义执行操作的接口。
- 具体命令类(Concrete Command Class):实现了命令接口,并拥有接收者对象的引用。
- 接收者(Receiver):知道如何实施与执行一个请求相关的操作,任何类都可以充当接收者。
- 调用者(Invoker):请求一个命令对象执行一个请求。
命令模式的优缺点
优点
- 降低耦合度:
- 请求发送者不需要知道请求的实际接收者是谁,也不需要知道请求的执行细节。
- 接收者和请求发送者之间解耦,使得两者可以独立变化。
- 易于实现撤销和重做功能:
- 由于每个命令都是一个对象,因此可以保存以前的状态,并在需要时恢复。
- 支持事务回滚,因为可以存储多个命令,并按顺序或逆序执行。
- 易于支持事务处理:
- 可以将多个命令组合成一个宏命令(复合命令),从而实现整体的事务处理(要么全部成功,要么全部失败)。
- 易于支持队列和日志:
- 命令对象可以被放入队列中,按照顺序执行。
- 命令对象也可以被记录下来,用于审计或日志记录。
- 增加新的命令非常容易:
- 添加新命令只需要实现命令接口,而不需要修改已有的代码。
- 这符合开闭原则(Open-Closed Principle),即对扩展开放,对修改关闭。
- 增强安全性:
- 可以通过命令对象来验证权限,从而在执行之前检查是否有权限执行该命令。
缺点
- 可能导致系统复杂度增加:
- 如果系统中存在大量的命令类,可能会导致类的数量增加,进而使系统变得复杂。
- 每个命令都需要一个具体的类来实现,这可能会导致类爆炸。
- 内存消耗增加:
- 如果命令对象被频繁创建,特别是在需要撤销或重做功能时,可能会导致内存消耗增加。
- 特别是在没有及时清理不再需要的命令对象时,内存占用会更高。
- 性能问题:
- 命令模式增加了额外的抽象层,可能会导致一些性能上的损耗,尤其是在需要频繁执行命令的情况下。
- 可能不适合简单场景:
对于非常简单的应用场景,使用命令模式可能会显得过于复杂,没有必要引入这种模式。
UML 图
实现代码
命令接口 (CommandInterface)
这个接口声明了所有命令对象都应该遵循的方法签名。
public interface CommandInterface {
void execute();
}
具体命令类 (ConcreteCommandClass)
这个类负责调用接收者的 openMobile() 方法,并添加了一些额外的操作(如“掏出口袋”)。
public class ConcreteCommandClass implements CommandInterface{
private Receiver receiver;
public ConcreteCommandClass(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("掏出口袋");
receiver.openMobile();
}
}
具体命令类 (ConcreteCommandClass1)
这个类负责调用接收者的 closeMoblie() 方法,并添加了一些额外的操作(如“放回口袋”)。
public class ConcreteCommandClass1 implements CommandInterface{
private Receiver receiver;
public ConcreteCommandClass1(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
receiver.closeMoblie();
System.out.println("放回口袋");
}
}
接收者
这个类包含了实际要执行的操作。
public class Receiver {
public void openMobile(){
System.out.println("打开手机");
}
public void closeMoblie(){
System.out.println("关闭手机");
}
}
主调用 (Main)
在这里创建了具体的命令对象,并调用了它们的 execute() 方法。
public class Main {
public static void main(String[] args) {
ConcreteCommandClass concreteCommandClass = new ConcreteCommandClass(new Receiver());
concreteCommandClass.execute();
ConcreteCommandClass1 concreteCommandClass1 = new ConcreteCommandClass1(new Receiver());
concreteCommandClass1.execute();
}
}
这个示例展示了命令模式的基本应用,通过将请求封装成对象,使得发送请求的对象和执行请求的对象之间松耦合。
命令模式应用场景
-
用户界面设计中的按钮和菜单项
在图形用户界面(GUI)应用程序中,命令模式可以用来处理用户交互事件。例如,当用户点击一个按钮或选择一个菜单项时,可以创建一个命令对象来表示这个请求,然后在适当的时候执行该命令。 -
宏命令和批处理
命令模式非常适合于构建宏命令或批处理命令。可以将多个简单的命令组合成一个复合命令,这样就可以一次执行一系列操作。例如,在文本编辑器中,可以定义一个宏来执行一系列编辑命令。 -
支持撤销功能(Undo/Redo)
命令模式可以很容易地支持撤销和重做功能。每个命令对象都可以记录其执行前后的状态,这样在需要撤销操作时可以恢复到之前的状态。这对于许多应用程序来说都是非常有用的特性,比如文本编辑器、绘图工具等。 -
解耦发送者与接收者
命令模式使得发送者和接收者之间的耦合度降低。发送者只需要知道如何发送命令,而不需要关心命令的具体实现细节。这使得系统更加灵活,可以在运行时动态改变命令的执行方式。 -
支持事务处理
在某些情况下,命令模式可以用来模拟事务处理。一组命令可以作为一个整体来执行,如果其中一个命令失败,则整个事务回滚。这对于一些需要保证数据一致性的场景非常有用。 -
异步命令执行
命令模式可以用来支持异步命令执行。命令对象可以在创建时立即被处理,也可以存储起来稍后由调度器统一执行。这对于需要异步处理的任务队列非常有用。 -
多线程环境下的任务调度
在多线程环境中,命令模式可以用来封装任务,并将其放入线程池中执行。这样可以方便地管理任务的执行顺序和优先级。
实际案例
- 编辑器中的撤销功能:用户在编辑器中进行了一系列操作,每个操作都被封装成一个命令对象。这些命令对象可以存储在一个栈中,用户可以选择撤销上一步操作,实际上是从栈中取出最后一个命令对象并执行它的撤销方法。
- 智能家居控制系统:用户可以通过语音助手控制家中的各种设备(如灯光、空调等)。语音助手接收到指令后,可以创建一个命令对象来表示这个请求,并通过网络发送给相应的设备进行处理。