「全网最细 + 实战源码案例」设计模式——命令模式

简介: 命令模式(Command Pattern)是一种行为型设计模式,将请求封装成独立对象,从而解耦请求方与接收方。其核心结构包括:Command(命令接口)、ConcreteCommand(具体命令)、Receiver(接收者)和Invoker(调用者)。通过这种方式,命令的执行、撤销、排队等操作更易扩展和灵活。适用场景:1. 参数化对象以操作。2. 操作放入队列或远程执行。3. 实现回滚功能。4. 解耦调用者与接收者。优点:- 遵循单一职责和开闭原则。- 支持命令组合和延迟执行。- 可实现撤销、恢复功能。缺点:- 增加复杂性和类数量。

核心思想

  • 命令模式(Command Pattern)是一种行为型设计模式,将请求(操作)封装成一个独立对象,从而解耦请求方与接收方,使得命令的执行、撤销、排队等操作更易扩展和灵活。


结构

1. Command(命令接口)

  • 定义一个执行命令的接口,所有具体命令类都需实现此接口。

2. ConcretCommand(具体命令)

  • 实现 Command 接口,定义与接收者之间的绑定关系,负责调用接收者的相应操作方法。

3. Receiver(实现者/接收者)

  • 执行真正的命令(业务逻辑),任何类都能成为接收者,只要它能实现命令要求实现的功能。

4. Invoker(调用者/请求者)

  • 持有命令对象,负责在适当时刻调用命令的 execute() 方法,且不需要知道命令的具体实现细节。


现实世界类比


适用场景

1. 需要通过操作来参数化对象:

  • 命令模式可将特定的方法调用转化为独立对象。 这一改变也带来了许多有趣的应用: 你可以将命令作为方法的参数进行传递、 将命令保存在其他对象中, 或者在运行时切换已连接的命令等。
  • 举个例子: 你正在开发一个 GUI 组件 (例如上下文菜单), 你希望用户能够配置菜单项, 并在点击菜单项时触发操作。

2. 需要将操作放入队列、操作的执行或远程执行操作:

  • 同其他对象一样, 命令也可以实现序列化 (序列化的意思是转化为字符串), 从而能方便地写入文件或数据库中。 一段时间后, 该字符串可被恢复成为最初的命令对象。 因此, 你可以延迟或计划命令的执行。 但其功能远不止如此! 使用同样的方式, 你还可以将命令放入队列、 记录命令或者通过网络发送命令。
  • 示例:
import java.util.ArrayList;
import java.util.List;
// 命令接口
interface Command {
    void execute();
}
// 具体命令:发送通知
class SendNotificationCommand implements Command {
    private String message;
    public SendNotificationCommand(String message) {
        this.message = message;
    }
    @Override
    public void execute() {
        System.out.println("发送通知: " + message);
    }
}
// 任务调度器(支持延迟执行)
class TaskScheduler {
    private List<Command> taskQueue = new ArrayList<>();
    public void addTask(Command command) {
        taskQueue.add(command);
    }
    public void executeAll() {
        for (Command command : taskQueue) {
            command.execute();
        }
        taskQueue.clear();
    }
}
// 测试代码
public class CommandPatternDelayedDemo {
    public static void main(String[] args) throws InterruptedException {
        TaskScheduler scheduler = new TaskScheduler();
        // 添加任务,但不立即执行
        scheduler.addTask(new SendNotificationCommand("任务 1"));
        scheduler.addTask(new SendNotificationCommand("任务 2"));
        // 模拟延迟
        System.out.println("任务将在 3 秒后执行...");
        Thread.sleep(3000);
        // 现在执行所有任务
        scheduler.executeAll();
    }
}

3. 需要实现回滚功能:

  • 尽管有很多方法可以实现撤销和恢复功能, 但命令模式可能是其中最常用的一种。
  • 为了能够回滚操作, 你需要实现已执行操作的历史记录功能。 命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。
  • 这种方法有两个缺点。 首先, 程序状态的保存功能并不容易实现, 因为部分状态可能是私有的。 你可以使用备忘录模式来在一定程度上解决这个问题。
  • 其次, 备份状态可能会占用大量内存。 因此, 有时你需要借助另一种实现方式: 命令无需恢复原始状态, 而是执行反向操作。 反向操作也有代价: 它可能会很难甚至是无法实现。
  • 示例:
import java.util.Stack;
// 命令接口
interface Command {
    void execute();  // 执行命令
    void undo();     // 撤销命令
}
// 具体命令:写入文本
class WriteCommand implements Command {
    private StringBuilder document;
    private String text;
    public WriteCommand(StringBuilder document, String text) {
        this.document = document;
        this.text = text;
    }
    @Override
    public void execute() {
        document.append(text);
    }
    @Override
    public void undo() {
        document.delete(document.length() - text.length(), document.length());
    }
}
// 命令管理者(支持撤销/恢复)
class CommandManager {
    private Stack<Command> history = new Stack<>();
    public void executeCommand(Command command) {
        command.execute();
        history.push(command);
    }
    public void undoCommand() {
        if (!history.isEmpty()) {
            history.pop().undo();
        }
    }
}
// 测试代码
public class CommandPatternUndoDemo {
    public static void main(String[] args) {
        StringBuilder document = new StringBuilder();
        CommandManager manager = new CommandManager();
        // 写入 "Hello "
        Command cmd1 = new WriteCommand(document, "Hello ");
        manager.executeCommand(cmd1);
        System.out.println(document);  // 输出: Hello 
        // 写入 "World!"
        Command cmd2 = new WriteCommand(document, "World!");
        manager.executeCommand(cmd2);
        System.out.println(document);  // 输出: Hello World!
        // 撤销最近的写入
        manager.undoCommand();
        System.out.println(document);  // 输出: Hello 
        // 再次撤销
        manager.undoCommand();
        System.out.println(document);  // 输出: (空字符串)
    }
}

4. 需要解耦调用者与接收者


优点:

1. 遵循单一职责原则:

  • 解耦调用者与接收者。

2. 遵循开闭原则:

  • 在不修改客户端代码的前提下创建新的命令。

3. 命令组合:

  • 多个命令组成一个宏命令,来调用更多操作。

4. 可实现撤销、恢复功能:

  • 在命令模式中,每个操作都会封装成一个命令对象,这样我们可以将命令存入一个历史记录栈,然后在需要撤销时回滚。  

5. 可实现操作的延迟执行:

  • 命令模式将操作封装成一个对象,这个对象可以存储传输排队,并在需要时再执行。  

缺点:

1. 增加复杂性

2. 类数量增加


实现步骤

  1. 声明仅有一个执行方法的命令接口
  2. 抽取请求并使之成为实现命令接口的具体命令类 每个类都必须有一组成员变量来保存请求参数和对于实际接收者对象的引用 所有这些变量的数值都必须通过命令构造函数进行初始化
  3. 找到担任调用者职责的类。 在这些类中添加保存命令的成员变量。 发送者只能通过命令接口与其命令进行交互。 发送者自身通常并不创建命令对象, 而是通过客户端代码获取。
  4. 修改发送者使其执行命令 而非直接将请求发送给接收者
  5. 客户端必须按照以下顺序来初始化对象:
  1. 创建接收者。
  2. 创建命令, 如有需要可将其关联至接收者。
  3. 创建发送者并将其与特定命令关联。

示例

// 订单类
public class Order {
    private int diningTable;
    private Map<String, Integer> foodDir = new HashMap<>();
    public int getDiningTable() {
        return diningTable;
    }
    public void setDiningTable(int diningTable) {
        this.diningTable = diningTable;
    }
    public Map<String, Integer> getFoodDic() {
        return foodDir;
    }
    public void setFood(String foodName, int num) {
        this.foodDir.put(foodName, num);
    }
}
// 接收者——厨师
public class SeniorChef {
    public void makeFood(String foodName, int num){
        System.out.println(num + "份" + foodName);
    }
}
// 命令接口
public interface Command {
    void execute();
}
// 具体命令——点餐命令
public class OrderCommand implements Command{
    private SeniorChef receiver;
    private Order order;
    public OrderCommand(SeniorChef receiver, Order order) {
        this.receiver = receiver;
        this.order = order;
    }
    @Override
    public void execute() {
        System.out.println("点餐命令:" + order.getDiningTable() + "桌点的菜单:");
        Map<String, Integer> foodDic = order.getFoodDic();
        if (foodDic != null) {
            for (Map.Entry<String, Integer> entry : foodDic.entrySet()){
                receiver.makeFood(entry.getKey(), entry.getValue());
            }
        }
        System.out.println(order.getDiningTable() + "桌点餐完成!");
    }
}
// 调用者——服务员
public class Waiter {
    private ArrayList<Command> commands = new ArrayList<>();
    public void setCommand(Command cmd) {
        commands.add(cmd);
    }
    public void orderUp(){
        System.out.println("美女服务员:客人点菜完毕,开始通知厨子做菜-----");
        for (Command command : commands) {
            if (command != null) {
                command.execute();
            }
        }
    }
}
// 测试类
public class Client {
    public static void main(String[] args) {
        // 创建一个调用者对象——服务员
        Waiter invoke = new Waiter();
        // 创建1号订单
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.setFood("西红柿鸡蛋面", 2);
        order1.setFood("宫保鸡丁", 1);
        // 创建2号订单
        Order order2 = new Order();
        order2.setDiningTable(2);
        order2.setFood("板面", 2);
        order2.setFood("九转大肠", 1);
        // 创建一个接收者对象——厨师
        SeniorChef receiver = new SeniorChef();
        // 创建一个命令对象,让同一个厨师做2个订单
        OrderCommand orderCommand1 = new OrderCommand(receiver, order1);
        OrderCommand orderCommand2 = new OrderCommand(receiver, order2);
        // 设置命令
        invoke.setCommand(orderCommand1);
        invoke.setCommand(orderCommand2);
        // 点菜
        invoke.orderUp();
    }
}

在源码中的应用

// Runnable为命令接口
// 具体命令:实现了 Runnable 接口,封装了一个具体操作
class LightOnCommand implements Runnable {
    private Light light;
    public LightOnCommand(Light light) {
        this.light = light;
    }
    @Override
    public void run() {
        light.turnOn();
    }
}
// 具体命令:实现了 Runnable 接口,封装了一个具体操作
class LightOffCommand implements Runnable {
    private Light light;
    public LightOffCommand(Light light) {
        this.light = light;
    }
    @Override
    public void run() {
        light.turnOff();
    }
}
// 接收者:负责具体操作的类
class Light {
    public void turnOn() {
        System.out.println("The light is ON");
    }
    public void turnOff() {
        System.out.println("The light is OFF");
    }
}
// 客户端:创建命令并将其传递给调用者
public class CommandPatternExample {
    public static void main(String[] args) {
        // 创建接收者
        Light light = new Light();
        // 创建命令(Runnable 实现)
        Runnable lightOn = new LightOnCommand(light);
        Runnable lightOff = new LightOffCommand(light);
        // 调用者(线程)执行命令
        Thread thread1 = new Thread(lightOn);
        thread1.start();
        Thread thread2 = new Thread(lightOff);
        thread2.start();
    }
}

与其他模式的关系

  • 按照顺序将请求动态传递给一系列的潜在接收者 直至其中一名接收者对请求进行处理
  • 在发送者和请求者之间建立单向连接
  • 清除了发送者和请求者之间的直接连接 强制它们通过一个中介对象进行间接沟通
  • 允许接收者动态地订阅或取消接收请求
  • 责任链的管理者可使用命令模式实现 在这种情况下 你可以对由请求代表的同一个上下文对象执行许多不同的操作
  • 还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。
  • 你可以同时使用命令备忘录模式来实现 撤销 在这种情况下 命令用于对目标对象执行各种不同的操作 备忘录用来保存一条命令执行前该对象的状态
  • 命令策略模式看上去很像 因为两者都能通过某些行为来参数化对象 但是 它们的意图有非常大的不同
  • 你可以使用来将任何操作转换为对象 操作的参数将成为对象的成员变量 你可以通过转换来延迟操作的执行 将操作放入队列 保存历史命令或者向远程服务发送命令等
  • 另一方面 通常可用于描述完成某件事的不同方式 让你能够在同一个上下文类中切换算法
目录
相关文章
|
1天前
|
设计模式 存储 Java
「全网最细 + 实战源码案例」设计模式——责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,允许将请求沿着处理者链进行发送。每个处理者可以处理请求或将其传递给下一个处理者,从而实现解耦和灵活性。其结构包括抽象处理者(Handler)、具体处理者(ConcreteHandler)和客户端(Client)。适用于不同方式处理不同种类请求、按顺序执行多个处理者、以及运行时改变处理者及其顺序的场景。典型应用包括日志处理、Java Web过滤器、权限认证等。
33 13
「全网最细 + 实战源码案例」设计模式——责任链模式
|
3天前
|
设计模式 算法 开发者
「全网最细 + 实战源码案例」设计模式——策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,用于定义一系列可替换的算法或行为,并将它们封装成独立的类。通过上下文持有策略对象,在运行时动态切换算法,提高代码的可维护性和扩展性。适用于需要动态切换算法、避免条件语句、经常扩展算法或保持算法独立性的场景。优点包括符合开闭原则、运行时切换算法、解耦上下文与策略实现、减少条件判断;缺点是增加类数量和策略切换成本。示例中通过定义抽象策略接口和具体策略类,结合上下文类实现动态算法选择。
37 8
「全网最细 + 实战源码案例」设计模式——策略模式
|
3天前
|
设计模式 SQL 算法
「全网最细 + 实战源码案例」设计模式——模板方法模式
模板方法模式是一种行为型设计模式,定义了算法的骨架并在父类中实现不变部分,将可变部分延迟到子类实现。通过这种方式,它避免了代码重复,提高了复用性和扩展性。具体步骤由抽象类定义,子类实现特定逻辑。适用于框架设计、工作流和相似算法结构的场景。优点包括代码复用和符合开闭原则,缺点是可能违反里氏替换原则且灵活性较低。
41 7
「全网最细 + 实战源码案例」设计模式——模板方法模式
|
5天前
|
设计模式 存储 安全
「全网最细 + 实战源码案例」设计模式——组合模式
组合模式(Composite Pattern)是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构。它允许客户端以一致的方式对待单个对象和对象集合,简化了复杂结构的处理。组合模式包含三个主要组件:抽象组件(Component)、叶子节点(Leaf)和组合节点(Composite)。通过这种模式,客户端可以统一处理简单元素和复杂元素,而无需关心其内部结构。适用于需要实现树状对象结构或希望以相同方式处理简单和复杂元素的场景。优点包括支持树形结构、透明性和遵循开闭原则;缺点是可能引入不必要的复杂性和过度抽象。
64 22
|
2月前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
125 11
|
3月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
15天前
|
设计模式
「全网最细 + 实战源码案例」设计模式——模式扩展(配置工厂)
该设计通过配置文件和反射机制动态选择具体工厂,减少硬编码依赖,提升系统灵活性和扩展性。配置文件解耦、反射创建对象,新增产品族无需修改客户端代码。示例中,`CoffeeFactory`类加载配置文件并使用反射生成咖啡对象,客户端调用时只需指定名称即可获取对应产品实例。
75 40
|
16天前
|
设计模式 关系型数据库
「全网最细 + 实战源码案例」设计模式——简单工厂模式
简单工厂模式是一种创建型设计模式,通过工厂类根据传入参数创建不同类型的对象,也称“静态工厂方法”模式。其结构包括工厂类、产品接口和具体产品类。优点是封装性强、代码复用性好;缺点是扩展性差,增加新产品时需修改工厂类代码,违反开闭原则。适用于对象种类较少且调用者无需关心创建细节的场景。
49 19
|
5月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
14天前
|
设计模式 Java
「全网最细 + 实战源码案例」设计模式——生成器模式
生成器模式(Builder Pattern)是一种创建型设计模式,用于分步骤构建复杂对象。它允许用户通过控制对象构造的过程,定制对象的组成部分,而无需直接实例化细节。该模式特别适合构建具有多种配置的复杂对象。其结构包括抽象建造者、具体建造者、指挥者和产品角色。适用于需要创建复杂对象且对象由多个部分组成、构造过程需对外隐藏或分离表示与构造的场景。优点在于更好的控制、代码复用和解耦性;缺点是增加复杂性和不适合简单对象。实现时需定义建造者接口、具体建造者类、指挥者类及产品类。链式调用是常见应用方式之一。
48 12