1. 概念
命令模式(Command Pattern)是一种行为型设计模式,也被称为动作模式或事务模式。它的核心思想是将一个请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化。对请求排队或记录,以及支持可撤销的操作。命令模式的主要目的是将发出请求的对象和执行请求的对象解耦。
2. 原理结构图
Command :这是一个接口,它声明了执行操作的方法。这个接口是所有具体命令类的基础,确保它们具有统一的执行方法调用方式。ConcreteCommand :这是实现了Command接口的具体类。它定义了接收者如何进行具体的操作执行。一个具体的命令类通常会持有一个对接收者的引用,并通过调用接收者的方法来完成请求的处理。Receiver :这是知道如何实施与执行请求相关的操作的类,也就是实际执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。Invoker :这是负责调用命令对象执行请求的类,它会持有一个命令对象,并在某个时间点触发命令的执行。调用者作为一个中介,将发起请求的对象和执行请求的对象解耦。
3. 代码示例
3.1 示例1–在线购物系统
实现一个在线购物系统。以下是一个简单的Java代码实现:
import java. util. ArrayList ;
import java. util. List ;
interface Command { void execute ( ) ;
}
class AddToCartCommand implements Command { private ShoppingCart cart; private Product product; public AddToCartCommand ( ShoppingCart cart, Product product) { this . cart = cart; this . product = product; } @Override public void execute ( ) { cart. addProduct ( product) ; }
}
class RemoveFromCartCommand implements Command { private ShoppingCart cart; private Product product; public RemoveFromCartCommand ( ShoppingCart cart, Product product) { this . cart = cart; this . product = product; } @Override public void execute ( ) { cart. removeProduct ( product) ; }
}
class ShoppingCart { private List < Product > products = new ArrayList < > ( ) ; public void addProduct ( Product product) { products. add ( product) ; System . out. println ( "Added " + product. getName ( ) + " to the shopping cart." ) ; } public void removeProduct ( Product product) { products. remove ( product) ; System . out. println ( "Removed " + product. getName ( ) + " from the shopping cart." ) ; }
}
class User { private Command command; public void setCommand ( Command command) { this . command = command; } public void executeCommand ( ) { command. execute ( ) ; }
}
class Product { private String name; public Product ( String name) { this . name = name; } public String getName ( ) { return name; }
} public class ShoppingSystemDemo { public static void main ( String [ ] args) { User user = new User ( ) ; ShoppingCart cart = new ShoppingCart ( ) ; Product product1 = new Product ( "Apple" ) ; Product product2 = new Product ( "Banana" ) ; user. setCommand ( new AddToCartCommand ( cart, product1) ) ; user. executeCommand ( ) ; user. setCommand ( new RemoveFromCartCommand ( cart, product1) ) ; user. executeCommand ( ) ; }
}
Added Apple to the shopping cart.
Removed Apple from the shopping cart.
在这个例子中,定义了一个命令接口Command,以及两个具体的命令类AddToCartCommand和RemoveFromCartCommand。这些命令类实现了Command接口,并在execute()方法中执行相应的操作。接收者类ShoppingCart负责处理添加和移除商品的操作。调用者类User负责设置命令并执行命令。最后,在main方法中创建了用户、购物车和产品对象,并通过命令模式实现了添加和移除商品的功能。
3.2 示例2
实现一个支持撤销功能的文本编辑器。以下是一个简单的Java代码实现:
interface Command { void execute ( ) ; void undo ( ) ;
}
class AddTextCommand implements Command { private TextEditor editor; private String text; public AddTextCommand ( TextEditor editor, String text) { this . editor = editor; this . text = text; } @Override public void execute ( ) { editor. addText ( text) ; } @Override public void undo ( ) { editor. deleteText ( editor. getContentLength ( ) - text. length ( ) , editor. getContentLength ( ) ) ; }
}
class DeleteTextCommand implements Command { private TextEditor editor; private int startIndex; private int endIndex; private String deletedText; public DeleteTextCommand ( TextEditor editor, int startIndex, int endIndex) { this . editor = editor; this . startIndex = startIndex; this . endIndex = endIndex; this . deletedText = editor. getSubstring ( startIndex, endIndex) ; } @Override public void execute ( ) { editor. deleteText ( startIndex, endIndex) ; } @Override public void undo ( ) { editor. addText ( deletedText) ; }
}
class TextEditor { private StringBuilder content = new StringBuilder ( ) ; public void addText ( String text) { content. append ( text) ; System . out. println ( "Added text: " + text) ; } public void deleteText ( int startIndex, int endIndex) { if ( startIndex >= 0 && endIndex < content. length ( ) ) { content. delete ( startIndex, endIndex) ; System . out. println ( "Deleted text from index " + startIndex + " to " + endIndex) ; } else { System . out. println ( "Invalid index range for deletion" ) ; } } public int getContentLength ( ) { return content. length ( ) ; } public String getSubstring ( int startIndex, int endIndex) { return content. substring ( startIndex, endIndex) ; }
}
class User { private Command command; public void setCommand ( Command command) { this . command = command; } public void executeCommand ( ) { command. execute ( ) ; } public void undoCommand ( ) { command. undo ( ) ; }
} public class TextEditDemo { public static void main ( String [ ] args) { User user = new User ( ) ; TextEditor editor = new TextEditor ( ) ; user. setCommand ( new AddTextCommand ( editor, "Hello, world!" ) ) ; user. executeCommand ( ) ; user. setCommand ( new DeleteTextCommand ( editor, 0 , 5 ) ) ; user. executeCommand ( ) ; user. undoCommand ( ) ; }
}
Added text: Hello , world!
Deleted text from index 0 to 5
Added text: Hello
在这个例子中,定义了一个命令接口Command,以及两个具体的命令类AddTextCommand和DeleteTextCommand。这些命令类实现了Command接口,并在execute()方法中执行相应的操作。同时,它们还实现了undo()方法,用于撤销操作。接收者类TextEditor负责处理添加和删除文本的操作。调用者类User负责设置命令并执行命令。最后,在main方法中创建了用户和文本编辑器对象,并通过命令模式实现了添加和删除文本的功能,并支持撤销操作。
4. 优缺点
主要作用 实现调用操作和实现操作的解耦,以及提供记录、撤销/重做和事务等功能处理。 优点 降低系统耦合度 :命令模式通过将请求调用者和接收者解耦,使得双方不必相互依赖,从而降低了系统各部分之间的直接联系。良好的封装性 :每个命令都被封装成对象,客户端只需调用相应的命令对象而无需关心具体的执行细节,增强了代码的模块性和复用性。高扩展性 :在命令模式中,新增命令通常不需要从零开始编写,可以利用现有的接收者类和命令类,通过组合或继承来实现,这使得扩展新功能变得简单方便。方便实现撤销/重做 :命令模式可以与备忘录模式结合使用,便于实现对已执行命令的撤销(Undo)和重做(Redo)操作,这对于很多交互式应用程序来说是非常重要的功能。 缺点 实现复杂性 :相比直接调用操作,命令模式引入了命令对象和接收者对象之间的额外间接层,这增加了系统的实现复杂性。维护成本 :随着命令类数量的增加,维护成本可能会上升,特别是在命令种类非常多的情况下,管理这些命令可能会变得繁琐。性能影响 :每次调用命令都会产生新的命令对象,这可能会增加内存占用和垃圾回收的频率,从而对系统性能产生一定的影响。使用限制 :如果一个请求的执行需要多个接收者参与,那么命令模式可能需要额外的工作来协调多个接收者之间的交互。过度设计 :对于简单的系统或者不需要解耦请求发起者和执行者的系统,使用命令模式可能会导致设计过度。
5. 应用场景
5.1 主要包括以下几个方面
自动化操作 :在需要程序自动执行一系列步骤的情境中,命令模式可以显著减少用户的手动操作。例如,在计算机安装程序中,程序可以根据用户的选择自动执行一系列操作,而无需用户手动输入每一步的指令。多线程操作 :在多线程环境中,多个线程可以发出操作命令,而程序可以在后台自动处理这些命令,无需等待线程完成。这有助于提高系统的响应速度和效率。存储与回放 :命令模式也常用于存储一系列操作,如用户操作数据库的一系列命令。这些命令可以被存储起来,并在需要时重新加载执行,从而让用户能够继续之前的操作,无需重新输入指令。请求调用者与接收者解耦 :当需要将请求发送者和接收者解耦,使得发送者不需要知道接收者的具体实现细节时,命令模式非常适用。这有助于降低系统的耦合度,提高代码的可读性和可维护性。支持撤销和重做操作 :命令模式可以记录命令的执行历史,从而支持撤销和重做操作。这在文本编辑器、绘图工具等需要频繁修改和回退的应用中非常有用。实现事务性操作 :通过将多个命令组合成一个复合命令,命令模式可以实现事务性操作。这确保了要么所有命令都成功执行,要么在发生错误时所有命令都不执行,从而保证了数据的完整性和一致性。
5.2 实际应用
文本编辑器 :在文本编辑器中,用户的操作如复制、粘贴、撤销等都可以被视作命令。使用命令模式可以方便地实现这些操作的记录和撤销。GUI按钮与操作 :图形用户界面中的按钮通常对应特定的操作,例如“保存”按钮会触发保存操作。使用命令模式可以将每个按钮的行为封装成具体的命令对象,使得按钮与实际执行的操作解耦。事务处理系统 :在数据库事务处理中,一个事务可能包含多个操作步骤。使用命令模式可以将这些步骤封装成命令对象,确保事务的原子性。宏命令 :在软件系统中,用户可能需要通过一个操作来触发一系列行为。命令模式可以用来创建一个宏命令,该命令包含多个子命令,从而简化用户操作。调度系统 :在任务调度系统中,可以使用命令模式来表示待执行的任务,这些任务可以被安排在特定时间执行或按照某种规则进行排队。游戏 :在游戏中,角色的每一个动作(如移动、攻击、跳跃)都可以被抽象为命令,游戏循环可以作为调用者来执行这些命令。遥控器 :在智能家居系统中,遥控器发送的命令(如开灯、调整温度)可以通过命令模式来实现,使得命令的发送者和接收者可以独立变化。工作流引擎 :在工作流引擎中,业务流程的各个步骤可以被设计为命令对象,工作流引擎负责调用这些命令来驱动流程的前进。
6. JDK中的使用
Runnable接口和Callable接口 :Runnable和Callable接口是JDK中线程任务执行的基础,它们都体现了命令模式的思想。 请求封装为对象 :Runnable和Callable接口的实例代表了一个可以被线程执行的任务(即命令)。这些任务可以是任何逻辑,只要实现了相应的接口方法。解耦 :通过传递Runnable或Callable对象给线程(如Thread类或线程池),我们实现了任务提交者和任务执行者之间的解耦。提交者不需要知道任务的具体实现细节,只需要关心任务的执行。灵活性 :由于任务被封装为对象,我们可以轻松地将任务传递给不同的线程或线程池执行,也可以将任务存储在队列中等待执行,从而实现更高的灵活性。 Comparator接口 :Comparator接口用于定义对象的比较逻辑,它常被用于集合的排序操作 请求封装 :Comparator的实例代表了一种比较命令,它定义了如何比较两个对象。通过传递不同的Comparator对象给排序方法,我们可以实现不同的排序逻辑。灵活性 :由于比较逻辑被封装为对象,我们可以轻松地为同一个集合定义多种排序方式,而无需修改集合本身或对象的内部状态。解耦 :排序方法和比较逻辑之间的解耦使得代码更加清晰和易于维护。排序方法只需要关注如何根据提供的比较逻辑来排序对象,而不需要关心比较逻辑的具体实现。
7. 注意事项
注意解耦 :确保请求发送者与接收者之间解耦,提高系统灵活性。控制命令类数量 :避免创建过多的具体命令类,防止系统复杂化。考虑撤销与重做 :如果需要,实现命令的撤销和重做功能,增强用户体验。安全性与权限 :确保命令执行的安全性,并进行必要的权限检查。命令命名与封装 :命令命名应清晰,封装完整的请求信息,确保一致性和完整性。
8. 命令模式 VS 外观模式
模式 类型 目的 模式架构主要角色 应用场景 命令模式 行为型 实现发送者和接收者之间的解耦,使发送者不需要知道接收者的具体实现细节,只需要知道如何发送命令即可 命令接口(Command)、具体命令类(ConcreteCommand)、请求者(Invoker)、接收者(Receiver) 适用于需要记录、处理或执行操作的场景 外观模式 结构型 简化复杂系统的接口 外观类(Facade)和 子系统类(Subsystem) 适用于需要为复杂的子系统提供一个简单接口的场景