返回 导航

其他

hangge.com

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)接着我们定义了两个实现了命令接口的具体命令类:TurnOnLightCommandTurnOffLightCommand,分别用于打开和关闭电灯。
// 开灯命令
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)

回到顶部