Java - GoF设计模式详解16(命令模式)
作者:hangge | 2023-06-13 10:05
十六、命令模式
1,基本介绍
(1)命令模式(Command )提供了一种将请求封装成对象的方法,从而使我们可以用不同的请求对客户进行参数化。
(2)命令模式的优点包括:
- 可以将请求的发送者和接收者解耦。发送者和接收者可以独立地变化。
- 可以支持可撤销操作。
- 可以方便地实现对请求的日志记录、恢复和重做。
- 可以将复杂的请求封装成一个单独的命令对象,这样就可以将该命令对象存储、传递或在队列中传递。
(3)该模式中包含的角色及其职责如下:
- 命令(Command):定义命令的接口,声明执行的操作。
- 具体命令(Concrete Command):命令的实现,通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 接收者(Receiver):真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者(Invoker):负责调用命令。通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
- 客户端(Client):创建具体命令对象,并设定它的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个 Client 称为装配者会更好理解,因为真正使用命令的客户端是从 Invoker 来触发执行。

2,使用样例
(1)下面我们演示如何在应用程序中使用命令模式来控制电灯。首先我们定义一个 Light 类(即接收者),该类表示电灯,并提供了 turnOn() 和 turnOff() 方法用于打开和关闭电灯。
// 电灯
public class Light {
public void turnOn() {
System.out.println("打开电灯");
}
public void turnOff() {
System.out.println("关闭电灯");
}
}
(2)然后我们定义一个 Command 接口,该接口有一个 execute() 方法,用于执行命令。
// 命令接口
public interface Command {
void execute();
}
(3)接着我们定义了两个实现了命令接口的具体命令类:TurnOnLightCommand 和 TurnOffLightCommand,分别用于打开和关闭电灯。
// 开灯命令
public class TurnOnLightCommand implements Command {
private Light light;
public TurnOnLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
// 关灯命令
public class TurnOffLightCommand implements Command {
private Light light;
public TurnOffLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
}
(4)接下来我们定义了 RemoteControl 类,该类表示遥控器(即调用者 Invoker),并提供了 setCommand() 方法用于设置命令,以及 pressButton() 方法用于执行命令。
// 遥控器
public class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
(5)最后在应用程序的 main() 方法中(该类即为客户端 Client),我们创建了一个电灯对象,然后创建了打开和关闭电灯命令的实例。接着我们创建了遥控器对象,并使用 setCommand() 方法设置打开电灯命令,然后调用 pressButton() 方法执行命令。最后,我们再次使用 setCommand() 方法设置关闭电灯命令,并再次调用 pressButton() 方法执行命令。
public class Test {
public static void main(String[] args) {
// 创建一个电灯对象
Light light = new Light();
// 创建打开和关闭电灯命令的实例
Command turnOnCommand = new TurnOnLightCommand(light);
Command turnOffCommand = new TurnOffLightCommand(light);
// 创建遥控器对象
RemoteControl remoteControl = new RemoteControl();
// 设置打开电灯命令
remoteControl.setCommand(turnOnCommand);
// 执行命令
remoteControl.pressButton();
// 设置关闭电灯命令
remoteControl.setCommand(turnOffCommand);
// 执行命令
remoteControl.pressButton();
}
}
附一:JDK 中的命令模式
(1)在 JDK 中,命令模式通常用于实现 GUI 组件的事件处理和菜单项的操作。例如,在 Swing 中,我们可以使用 java.awt.event.Action 接口来实现命令模式。Action 接口有一个 actionPerformed 方法,该方法可以被用作事件处理程序,当用户单击菜单项或按钮时,该方法会被调用。
(2)下面示例中我们创建了一个按钮和一个文本框,并创建了一个实现 Action 接口的命令类。当用户点击按钮时,这个命令的 actionPerformed 方法将会被调用,并在文本框中输出信息。
public class Test {
public static void main(String[] args) {
// 创建一个窗口
JFrame frame = new JFrame("My Window");
frame.setLayout(new BorderLayout());
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 创建一个文本字段
JTextField textField = new JTextField();
frame.add(textField, BorderLayout.CENTER);
// 创建一个按钮
JButton button = new JButton("点击按钮");
frame.add(button,BorderLayout.NORTH);
// 创建一个命令,当用户点击按钮时,将会执行这个命令
Action command = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
textField.setText("按钮被点击!");
}
};
// 将命令附加到按钮上
button.addActionListener(command);
// 显示窗口
frame.setVisible(true);
}
}
附二:Spring 中的命令模式
1,Spring 中的事务管理
(1)Spring 使用命令模式来封装事务操作,将事务操作作为命令对象进行封装,然后将其交给事务管理器进行处理。
(2)在 Spring 中,事务管理是通过 PlatformTransactionManager 接口来实现的。Sping 事务管理使用了命令模式,将事务操作封装为了 PlatformTransactionManager 接口的实现类,然后将其交给事务管理器进行处理。PlatformTransactionManager 接口定义了一系列的事务操作方法,如下所示:
接口说明:TransactionDefinition 接口定义了事务定义相关的属性,如事务传播行为、隔离级别、超时时间等。TransactionStatus 接口定义了事务状态相关的属性和操作方法,如是否新事务、是否已经被标记为回滚、是否已经完成等。
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition var1)
throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
(3)下面是一个示例代码,展示了如何使用 Spring 中的事务管理。UserService 类使用 @Transactional 注解标注了 addUser() 方法,表示该方法需要进行事务管理。在执行 addUser() 方法时,Spring 会自动调用 PlatformTransactionManager 接口的实现类进行事务操作,实现事务的开启、提交、回滚等功能。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformTransactionManager transactionManager;
@Transactional
public void addUser(User user) {
userDao.addUser(user);
}
}
2,Spring 中的异步处理
(1)Spring 使用命令模式来实现异步处理功能,将异步处理操作封装为命令对象,然后交给异步任务执行器进行处理。
(2)在 Spring 中,可以通过 @Async 注解和 TaskExecutor 接口来实现异步处理功能。
- @Async 注解用于标注一个方法或类,表示该方法或类中的方法可以被异步执行。
- TaskExecutor 接口定义了一系列的异步任务执行方法,如下所示。
可以看到,Spring 中的异步处理使用了命令模式,将异步任务封装为了 Runnable 接口的实现类,然后交给 TaskExecutor 接口的实现类进行处理。
public interface TaskExecutor {
void execute(Runnable var1);
void execute(Runnable var1, long var2);
}
(3)下面是一个示例代码,展示了如何使用 Spring 中的异步处理。UserService 类的 asyncAddUser() 方法使用了 @Async 注解,表示该方法可以被异步执行。当调用 asyncAddUser() 方法时,Spring 会自动将方法封装为 Runnable 接口的实现类,然后交给 TaskExecutor 接口的实现类进行处理。
@Service
public class UserService {
@Async
public void asyncAddUser(User user) {
// 进行用户添加操作
}
}
3,Spring 中的定时调度任务
(1)Spring 使用命令模式来实现调度任务功能,将调度任务作为命令对象进行封装,然后交给调度器进行处理。
(2)在 Spring 中,可以使用 @Scheduled 注解和 TaskScheduler 接口来实现调度任务功能。
- @Scheduled 注解用于标注一个方法,表示该方法可以被定时执行。
- TaskScheduler 接口定义了一系列的调度任务执行方法,如下所示。
可以看到,Spring 中的调度任务使用了命令模式,将调度任务封装为了 Runnable 接口的实现类,然后交给 TaskScheduler 接口的实现类进行处理。
public interface TaskScheduler {
ScheduledFuture schedule(Runnable var1, Trigger var2);
ScheduledFuture schedule(Runnable var1, Date var2);
ScheduledFuture scheduleAtFixedRate(Runnable var1, Date var2, long var3);
ScheduledFuture scheduleAtFixedRate(Runnable var1, long var2);
ScheduledFuture scheduleWithFixedDelay(Runnable var1, Date var2, long var3);
ScheduledFuture scheduleWithFixedDelay(Runnable var1, long var2);
}
(3)下面是一个示例代码,展示了如何使用 Spring 中的调度任务。ScheduledTask 类的 executeTask() 方法使用了 @Scheduled 注解,表示该方法可以被定时执行。当调度器启动时,Spring 会自动将 executeTask() 方法封装为 Runnable 接口的实现类,然后交给 TaskScheduler 接口的实现类进行处理,按照 cron 表达式规定的时间周期执行 executeTask() 方法。
@Component
public class ScheduledTask {
@Scheduled(cron = "0 0/1 * * * *")
public void executeTask() {
// 进行定时任务处理
}
}
4,Spring 中的 JdbcTemplate 类
(1)Spring 中的 JdbcTemplate 类使用了命令模式,将 SQL 操作封装为命令对象,然后交给 JdbcTemplate 执行。提示:准确来说 JdbcTemplate 并不是遵从标准的命令模式,而是采用了命令模式思想。JdbcTemplate 不仅是调用者角色,其内部类 QueryStatementCallback 又充当了接收者和具体命令角色。
(2)下面是使用 JdbcTemplate 类的 query() 方法查询数据库样例:
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
// 获取多条数据
public List<User> getAllUsers() {
return jdbcTemplate.query("SELECT * FROM users",
new BeanPropertyRowMapper<>(User.class));
}
}
(3)而 query() 方法内容如下,可以看到这里面有个内部类 QueryStatementCallback 实现了 StatementCallback 接口,该接口只有唯一的 doInStatement 方法。最后就是创建了这个内部类的实例传给 execute 方法并放回结构。从源码可以发现:
- StatementCallback 接口可以看做是命令接口。
- 匿名内部类 QueryStatementCallback 是该命令接口的一个具体实现命令,同时也充当命令接收者。

(4)execute(StatementCallback<T> action) 方法代码如下,该方法内部调用 action.doInStatement 方法。不同的实现 StatementCallback 接口的对象,对应不同的 doInStatemnt 实现逻辑。因此 JdbcTemplate 是调用者角色。
全部评论(0)