引言
想象一下,你在一个忙碌的厨房里,厨师们正忙于准备各种菜肴。每当服务员带来一个新订单时,他们不会直接对厨师说需要做什么菜。相反,订单被写在纸上,并放置在一个订单队列中。厨师们会一个接一个地取订单,并准备相应的菜肴。这里,每个订单就像是一个“命令”,它包含了需要执行的所有信息。在软件开发中,我们处理命令时也可以采取类似的方式。命令模式允许我们以同样的方式封装所有信息,这些信息需要执行某项操作或触发某个事件,然后像传递订单一样传递这些命令。
命令模式简介
定义与用途
命令模式(Command Pattern)是一种行为型设计模式,它将一个请求或简单操作封装为一个对象。这种模式允许用户根据不同的请求将方法调用、请求或操作封装到单独的对象中,并将这些对象传递给调用者。调用者随后可以根据需求执行这些操作。这种模式通常用于:队列操作、回调操作和对请求的日志记录。
实现方式
实现命令模式涉及以下几个关键组件:
- 命令接口(Command):定义了执行操作的接口。
- 具体命令(Concrete Command):实现命令接口,定义接收者上执行的动作。
- 接收者(Receiver):执行与请求相关联的操作。
- 调用者(Invoker):持有命令,并在某个时间点调用命令的执行方法。
- 客户端(Client):创建具体命令,并设置其接收者。
使用场景
命令模式适用于以下场景:
当需要将请求发送者和请求接收者解耦时。
当需要对请求的排队、记录或可撤销操作时。
在需要支持宏命令和事务系统的场景中。
例如:
- GUI按钮和菜单项:GUI的按钮和菜单项通常使用命令模式来处理用户输入。
- 事务型行为:命令模式可以用来实现事务系统,其中命令有执行和回滚两种操作。
- 多级撤销功能:在支持撤销操作的系统中,命令模式可以用来记录操作的历史,以便用户可以撤销或重做它们。
优势与劣势
- 优势
降低系统的耦合度:命令模式将调用操作的对象与知道如何实施该操作的对象解耦。
增强了扩展性:可以很容易地增加新的命令。
可以组合命令:可以组合多个命令,实现复杂的功能。 - 劣势
可能会导致某些系统有过多的具体命令类。
在Spring框架中的应用
JdbcTemplate 是Spring框架中使用命令模式的一个很好的例子。在JdbcTemplate的上下文中,命令模式提供了一种强大的机制来抽象和封装对数据库的各种操作。它允许JdbcTemplate通过执行各种StatementCallback实现来灵活地执行各种操作,同时保持其自身的简洁和不涉及具体数据库操作的逻辑。
(1)命令接口(Command)- StatementCallback
StatementCallback<T>接口 在这个上下文中类似于命令模式中的命令接口。它定义了一个doInStatement(Statement stmt)方法,该方法接收一个Statement对象作为参数。不同的实现将对数据库执行不同的操作。
(2)具体命令和接收者 - QueryStatementCallBack和其他实现
具体命令 由实现StatementCallback接口的类表示,例如QueryStatementCallBack。这些类封装了对数据库执行特定操作的逻辑。
同时充当命令接收者:在这种情况下,具体命令类(例如QueryStatementCallBack)不仅表示命令,还包含执行命令所需的逻辑,因此它们也扮演了接收者的角色。它们知道如何操作数据库来执行特定的操作。
(3)命令调用者 - JdbcTemplate
JdbcTemplate 是命令调用者。它提供了一个execute方法,该方法接受一个StatementCallback对象。JdbcTemplate不关心StatementCallback的具体操作,它只调用doInStatement方法并传递必要的Statement对象。这样,JdbcTemplate就可以执行各种不同的数据库操作,而不需要知道具体的细节。
(4)不同的具体命令实现
StatementCallback的其他实现,如QueryStatementCallback,为不同类型的数据库操作提供了具体实现。每个实现都有自己独特的doInStatement方法,它们封装了如何使用传递给它们的Statement来执行操作。
股票示例
步骤 1:创建命令接口
首先定义了一个 Order 接口,作为命令的角色。
public interface Order {
void execute();
}
这个接口定义了一个execute方法,所有的命令都将实现这个方法来执行它们的操作。
步骤 2:创建请求类
创建了一个 Stock 类,作为请求的角色。
public class Stock {
private String name = "ABC";
private int quantity = 10;
public void buy(){
System.out.println("股票 [ 名称: "+name+", 数量: " + quantity +" ] 购买了");
}
public void sell(){
System.out.println("股票 [ 名称: "+name+", 数量: " + quantity +" ] 卖出了");
}
}
这个类代表一个股票,有买入和卖出的操作。
步骤 3:创建具体命令类
创建了实现Order接口的具体命令类。
public class BuyStock implements Order {
private Stock abcStock;
public BuyStock(Stock abcStock){
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.buy();
}
}
public class SellStock implements Order {
private Stock abcStock;
public SellStock(Stock abcStock){
this.abcStock = abcStock;
}
@Override
public void execute() {
abcStock.sell();
}
}
BuyStock和SellStock是具体的命令,分别代表购买股票和出售股票的操作。
步骤 4:创建命令调用者类
创建了一个 Broker 类,作为命令的调用者。
import java.util.ArrayList;
import java.util.List;
public class Broker {
private List<Order> orderList = new ArrayList<Order>();
public void takeOrder(Order order){
orderList.add(order);
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
Broker 可以接受和存储命令,然后一次性执行这些命令。它通过调用每个命令的execute方法来执行命令。
步骤 5:使用 Broker 类来接受和执行命令
public class CommandPatternDemo {
public static void main(String[] args) {
Stock abcStock = new Stock();
BuyStock buyStockOrder = new BuyStock(abcStock);
SellStock sellStockOrder = new SellStock(abcStock);
Broker broker = new Broker();
broker.takeOrder(buyStockOrder);
broker.takeOrder(sellStockOrder);
broker.placeOrders();
}
}
在这个客户端中,我们创建了买入和卖出股票的命令,然后通过Broker来执行它们。这个例子演示了命令模式如何用于参数化对象以及如何将请求排队等待执行。
代码地址
23种设计模式相关代码后续会逐步提交到github上,方便学习,欢迎指点:
代码地址
https://github.com/RuofeiSun/lf-23Pattern