详细说说命令模式

251 阅读10分钟

在软件设计中,命令模式是一种非常重要的设计模式。随着软件系统的不断发展,系统中的各个组件之间的耦合度越来越高,这会导致系统的复杂度和维护难度不断增加。为了解决这个问题,设计模式提供了很多解决方案,其中命令模式就是其中之一。

命令模式将请求操作封装成一个独立的对象,使得请求的发送者和接收者对象之间解耦。通过这种方式,命令模式可以提高系统的灵活性、可维护性和可扩展性,从而降低了系统的耦合度。在实际的软件设计中,命令模式被广泛应用于各种应用场景,如图形界面开发、网络通信、电子商务系统等。它可以帮助开发人员快速地实现各种请求操作,并且能够支持撤销和重做操作,提高了系统的交互性和用户体验。

什么是命令模式

官方文档描述:

命令模式将请求封装为一个对象,从而使得你能够通过不同的请求参数化客户端,将请求进行排队或记录,并支持可撤销的操作。

The Command pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

命令模式的作用:

  1. 将请求发送者和请求接收者进行解耦,从而减少它们之间的依赖关系,提高系统的灵活性和可扩展性。

  2. 支持撤销、重做和事务性操作,使得系统更加健壮和可靠。

  3. 支持命令的排队和记录,从而能够更好地管理和控制请求的执行顺序和时间。

  4. 使得代码更易于扩展和维护,因为每个命令对象都只封装了一次请求操作,而不需要修改请求发送者或请求接收者 的代码。

命令模式的结构

  • 命令接口(Command):定义执行操作的接口。
  • 具体命令(ConcreteCommand):实现命令接口,绑定接收者和一个动作。
  • 执行者(Invoker):调用命令对象以执行请求。
  • 接收者(Receiver):执行与请求相关的操作。

命令模式实现

一个常见的生活中的例子是使用遥控器控制电视机。遥控器可以被看做是一个命令对象,它封装了各种具体的请求,例如打开电视、关闭电视、切换频道等。电视机则是一个接收者对象,它知道如何执行这些请求操作。

当我们按下遥控器上的按钮时,遥控器会将相应的请求封装成一个命令对象,并将该对象发送给电视机进行处理。这样,电视机就不需要知道具体的请求发送者是谁,也不需要知道如何处理这些请求。通过将请求封装成一个独立的对象,我们能够更加灵活地管理和控制请求的执行顺序和时间。

命令接口(Command):定义执行操作的接口

public interface Command {
    void execute();
}

具体命令(ConcreteCommand):实现命令接口,绑定接收者一个动作

public class TurnOnCommand implements Command {
    private Television tv;

    // 构造函数,用于将接收者对象传递给命令对象
    public TurnOnCommand(Television tv) {
        this.tv = tv;
    }

    // 执行打开电视机命令
    public void execute() {
        tv.turnOn();
    }
}
public class TurnOffCommand implements Command {
    private Television tv;

    // 构造函数,用于将接收者对象传递给命令对象
    public TurnOffCommand(Television tv) {
        this.tv = tv;
    }

    // 执行关闭电视机命令
    public void execute() {
        tv.turnOff();
    }
}
public class ChangeChannelCommand implements Command {
    private Television tv;
    private int channel;

    // 构造函数,用于将接收者对象和命令参数传递给命令对象
    public ChangeChannelCommand(Television tv, int channel) {
        this.tv = tv;
        this.channel = channel;
    }

    // 执行切换频道命令
    public void execute() {
        tv.changeChannel(channel);
    }
}

执行者(Invoker):调用命令对象以执行请求

public class RemoteControl {
    private Command[] commands;

    public RemoteControl(Command[] commands) {
        this.commands = commands;
    }

    public void pressButton(int index) {
        if (index >= 0 && index < commands.length) {
            commands[index].execute();
        }
    }
}

这个类是命令模式中的调用者(Invoker)类,它包含一个命令数组,可以通过调用命令对象的execute()方法来执行命令操作。在这个类中,通过调用pressButton()方法来执行具体的命令操作。它接收一个整数参数index,表示要执行的命令对象的索引,然后通过索引获取对应的命令对象,然后调用该命令对象的execute()方法来执行命令操作。

接收者(Receiver):执行与请求相关的操作

public class Television {
    private int currentChannel = 1;
    private boolean isOn = false;

    // 打开电视机
    public void turnOn() {
        isOn = true;
        System.out.println("电视机已打开");
    }

    // 关闭电视机
    public void turnOff() {
        isOn = false;
        System.out.println("电视机已关闭");
    }

    // 切换频道
    public void changeChannel(int channel) {
        if (isOn) {
            currentChannel = channel;
            System.out.println("已切换到频道 " + channel);
        } else {
            System.out.println("电视机未打开");
        }
    }

    // 获取当前频道
    public int getCurrentChannel() {
        return currentChannel;
    }

    // 判断电视机是否打开
    public boolean isOn() {
        return isOn;
    }
}
  • turnOn() 方法用于打开电视机。
  • turnOff() 方法用于关闭电视机。
  • changeChannel(int channel) 方法用于切换频道。如果电视机已经打开,则将当前频道设置为指定的频道,并输出一条提示信息;否则输出一条错误信息。
  • getCurrentChannel() 方法用于获取当前频道。
  • isOn() 方法用于判断电视机是否打开。`

Client:调用代码

public class Client {
    public static void main(String[] args) {
        Television tv = new Television();
        Command[] commands = new Command[] {
                new TurnOnCommand(tv),
                new TurnOffCommand(tv),
                new ChangeChannelCommand(tv, 1),
                new ChangeChannelCommand(tv, 2),
                new ChangeChannelCommand(tv, 3)
        };
        RemoteControl remote = new RemoteControl(commands);

        remote.pressButton(0); // 打开电视机
        remote.pressButton(2); // 切换到频道1
        remote.pressButton(3); // 切换到频道2
        remote.pressButton(4); // 切换到频道3
        remote.pressButton(1); // 关闭电视机
    }
}

命令模式的优缺点

命令模式的优点

  1. 解耦:命令模式将请求发送者和接收者对象解耦,使得它们不需要了解彼此的存在,从而降低了系统的耦合度。例如,在一个智能家居系统中,通过命令模式,用户可以使用一个统一的遥控器来控制多种家电设备,而无需为每种设备单独配置控制器。

解耦指的是在软件设计中,将不同的组件之间的依赖关系降低到最小程度,使得它们可以独立地进行修改、扩展和维护。这样,当系统中的一个组件发生变化时,不会对其他组件产生影响,从而提高了系统的灵活性和可维护性。

比喻来说,就像是拼积木一样,把积木拼接成独立的小块,使得每个小块可以独立拆卸和组合,而不会影响到其他的小块,从而提高了灵活性和可维护性

  1. 可扩展性:命令模式支持对请求操作进行组合和分离,使得系统更容易扩展和维护。例如,在一个文本编辑器中,命令模式可以用于实现撤销、重做等功能。当需要添加新的文本操作时,只需创建一个新的命令类即可,无需修改现有代码。

  2. 可重用性:命令模式将请求操作封装成一个独立的对象,使得这些对象可以被重复使用,从而提高了系统的可重用性。例如,在一个图形绘制程序中,可以使用命令模式来保存用户的绘图操作,以便在需要时重新执行或撤销这些操作。

  3. 安全性:命令模式可以将请求操作的调用者和接收者对象解耦,使得系统更加安全可靠。例如,在一个在线支付系统中,可以使用命令模式来封装支付操作,从而确保支付请求的正确性和安全性。

命令模式的缺点

  1. 增加系统复杂度:命令模式需要定义多个类和对象来实现具体的请求操作,这可能会增加系统的复杂度和理解难度。例如,在一个简单的应用程序中,引入命令模式可能会导致过度设计,增加了不必要的开发和维护成本。

  2. 命令类可能过多:当系统中需要实现大量的具体请求操作时,可能会导致命令类过多,从而增加了系统的维护难度和开发成本。例如,在一个具有复杂功能的企业级应用程序中,可能需要实现数百个不同的命令类,这将大大增加代码的复杂性和管理难度。

  3. 性能问题:由于命令模式需要将请求操作封装成对象,因此可能会对系统的性能产生一定的影响。特别是在需要频繁执行请求操作时,可能会导致系统的性能下降。例如,在一个高并发的网络服务器中,使用命令模式可能会增加对象的创建和销毁开销,从而降低服务器的响应速度

命令模式在源码中的体现

spring框架

JdbcTemplate

JdbcTemplate是Spring框架中的核心组件之一,它提供了对数据库的操作接口,包括查询、更新、删除等操作。在JdbcTemplate中,使用命令模式来封装对数据库的操作,具体实现是通过将SQL语句和操作参数封装成一个对象,然后通过执行这个对象来完成数据库操作。

在JdbcTemplate中,命令模式的实现主要是通过三个接口来实现的:PreparedStatementCreatorPreparedStatementSetterRowMapper

首先是PreparedStatementCreator接口,它定义了一个用于创建PreparedStatement对象的方法createPreparedStatement(Connection con),其中con参数表示要使用的数据库连接对象。这个接口代表了命令模式中的抽象命令,它封装了SQL语句和操作参数,并且通过createPreparedStatement方法将它们转换成一个PreparedStatement对象。

接下来是PreparedStatementSetter接口,它定义了一个用于设置PreparedStatement对象参数的方法setValues(PreparedStatement ps),其中ps参数表示要设置的PreparedStatement对象。这个接口代表了命令模式中的具体命令,它接收一个PreparedStatement对象作为参数,并且通过setValues方法来设置PreparedStatement对象的参数。

最后是RowMapper接口,它定义了一个用于将ResultSet结果集转换成Java对象的方法mapRow(ResultSet rs, int rowNum),其中rs参数表示要转换的ResultSet结果集,rowNum参数表示结果集的当前行号。这个接口代表了命令模式中的接收者,它接收一个ResultSet对象作为参数,并且通过mapRow方法来将结果集转换成Java对象并返回。

在JdbcTemplate中,通过将具体命令(PreparedStatementSetter接口的实现类)和抽象命令(PreparedStatementCreator接口的实现类)作为参数传递给query方法,并且在方法内部调用它们的方法来完成具体的数据库操作。同时,也通过将RowMapper接口的实现类作为参数传递给query方法,并且在方法内部调用它的方法来将结果集转换成Java对象并返回。

通过这种方式,JdbcTemplate实现了对数据库操作的封装,并且将命令对象与具体操作解耦,使得代码更加灵活和可扩展。同时,也使得代码更加易于维护和重构,提高了软件开发的效率。

总之,命令模式是一种非常有用的设计模式,通过将请求封装为一个对象,使得系统更加灵活、可扩展和易于维护。