设计模式之命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,命令模式也支持可撤销的操作。

来解析这个定义:

  1. “将一个请求封装为一个对象”,请求原本是一个方法,现在要封装成一个对象,说明要新增类来完成
  2. “可以用不同的请求对客户进行参数化”,说明是将命令对象作为参数进行传递
  3. “队列”说明需要维护命令多个命令的列表队列。
  4. “撤销”说明有命令对象有undo撤销方法。

命令模式在设计模式中,算是一个比较不好理解的模式,很重要的原因是不清楚设计意图,不清楚不用这个模式前有何问题,这个模式带了哪些好处,能解决什么问题。

上一篇状态模式中,看到了状态模式抽离的是状态(属性),向上提成状态对象。有了这个基础,再来理解命令模式就相对简单了。命令模式抽离的是行为(方法),向上提成命令对象。

两者都通过“对象化”来解耦和扩展系统,但解决的问题不同:

  • 状态模式:处理对象内部状态驱动的行为变化。
  • 命令模式:处理行为请求的封装与调度。
💡
智能家居遥控器,假设我们有一个智能家居遥控器,可以控制 灯(Light) 和 风扇(Fan) 的开关。

我们直接在遥控器类中硬编码控制逻辑。

// 遥控器类(紧耦合,难以扩展)
class RemoteControl {
    private Light light;
    private Fan fan;

    public RemoteControl(Light light, Fan fan) {
        this.light = light;
        this.fan = fan;
    }

    // 直接调用设备方法
    public void pressLightButton() {
        if (light.isOn()) {
            light.off();
        } else {
            light.on();
        }
    }

    public void pressFanButton() {
        if (fan.isOn()) {
            fan.off();
        } else {
            fan.on();
        }
    }
}

// 灯类
class Light {
    private boolean isOn = false;
    public void on() { isOn = true; System.out.println("Light is ON"); }
    public void off() { isOn = false; System.out.println("Light is OFF"); }
    public boolean isOn() { return isOn; }
}

// 风扇类
class Fan {
    private boolean isOn = false;
    public void on() { isOn = true; System.out.println("Fan is ON"); }
    public void off() { isOn = false; System.out.println("Fan is OFF"); }
    public boolean isOn() { return isOn; }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Light light = new Light();
        Fan fan = new Fan();
        RemoteControl remote = new RemoteControl(light, fan);

        remote.pressLightButton(); // Light is ON
        remote.pressFanButton();   // Fan is ON
        remote.pressLightButton(); // Light is OFF
    }
}

这个实现中,遥控器直接依赖 Light 和 Fan,新增设备(如空调)必须修改 RemoteControl 类。每增加一个设备,就要在 RemoteControl 中添加新方法(如 pressACButton())。

还有一个重要的点,无法支持撤销/重做,没有记录操作历史的能力。这个是命令模式非常重要的设计意图。

我们引入 命令模式,将每个操作封装成独立的 Command 对象:

定义命令接口。

interface Command {
    void execute();
    void undo(); // 支持撤销
}

实现具体命令。

// 开灯命令
class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) { this.light = light; }
    public void execute() { light.on(); }
    public void undo() { light.off(); }
}

// 关灯命令
class LightOffCommand implements Command {
    private Light light;
    public LightOffCommand(Light light) { this.light = light; }
    public void execute() { light.off(); }
    public void undo() { light.on(); }
}

// 开风扇命令
class FanOnCommand implements Command {
    private Fan fan;
    public FanOnCommand(Fan fan) { this.fan = fan; }
    public void execute() { fan.on(); }
    public void undo() { fan.off(); }
}

// 关风扇命令
class FanOffCommand implements Command {
    private Fan fan;
    public FanOffCommand(Fan fan) { this.fan = fan; }
    public void execute() { fan.off(); }
    public void undo() { fan.on(); }
}

改造遥控器(Invoker)。

class RemoteControl {
    private Command[] onCommands; // 存储"开"命令
    private Command[] offCommands; // 存储"关"命令
    private Command lastCommand; // 记录最后执行的命令(用于撤销)

    public RemoteControl() {
        onCommands = new Command[2]; // 假设有2个设备
        offCommands = new Command[2];
    }

    // 设置按钮对应的命令
    public void setCommand(int slot, Command onCmd, Command offCmd) {
        onCommands[slot] = onCmd;
        offCommands[slot] = offCmd;
    }

    // 按下"开"按钮
    public void pressOnButton(int slot) {
        onCommands[slot].execute();
        lastCommand = onCommands[slot];
    }

    // 按下"关"按钮
    public void pressOffButton(int slot) {
        offCommands[slot].execute();
        lastCommand = offCommands[slot];
    }

    // 撤销上一步操作
    public void pressUndoButton() {
        if (lastCommand != null) {
            lastCommand.undo();
        }
    }
}

客户端代码。

public class Client {
    public static void main(String[] args) {
        // 1. 创建设备
        Light light = new Light();
        Fan fan = new Fan();

        // 2. 创建命令对象
        Command lightOn = new LightOnCommand(light);
        Command lightOff = new LightOffCommand(light);
        Command fanOn = new FanOnCommand(fan);
        Command fanOff = new FanOffCommand(fan);

        // 3. 配置遥控器
        RemoteControl remote = new RemoteControl();
        remote.setCommand(0, lightOn, lightOff); // 按钮0控制灯
        remote.setCommand(1, fanOn, fanOff);     // 按钮1控制风扇

        // 4. 测试
        remote.pressOnButton(0);  // Light is ON
        remote.pressOnButton(1);  // Fan is ON
        remote.pressUndoButton(); // Fan is OFF(撤销)
        remote.pressOffButton(0); // Light is OFF
    }
}

如此遥控器和设备完全解耦。支持动态更换命令、撤销操作、宏命令(组合多个命令)。这个例子清晰地展示了命令模式如何通过封装行为来提升系统的灵活性和可扩展性。

Read more

痛风带来的思考

昨晚一罐冰啤酒下去,睡觉时就感觉脚踝隐隐发作,果然早上起床直接下不来地。跟崴脚的感觉十分相似,无法行走,只能坐在一起上滑动,公司上班也去不了了,呆呆得躺在家里,下午疼痛感加剧,整个心思都在左脚的疼痛上,没有其他任何多余的精力去关注其他事情,而此刻的阳台,乃最美人间四月天,春日的微风吹拂着阳台的花儿,温暖的阳光抛洒下来,一切都如此惬意,而我却无心欣赏。 人在健康时,生活中有好多问题,但人在不健康时,生活中只剩一个问题。 我对这句话的理解更深刻了。人是健忘的,在疫情期间、在手术期间,这种感悟其实已经很深刻了,但是病情好转之后,人还是会被日常的琐碎、工作的烦扰搅乱心绪,没有专注的去享受生活本身的美好。 幸福的秘密在于,去享受我们所拥有的,而不是顽固的去追求所没有拥有的。阳光、草木、微风,都是幸福的玩意儿,应尽情的享受。 再等两天,脚完全恢复好了,身体健健康康后,我要以更轻盈的姿态去生活,不纠结他人的看法,不执着别人的认可,关注自己的能力,享受拥有的生后。 还有一个反省,针对咖啡、酒、烟,

By 李浩

设计模式之状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 来解析这个定义: 1. “内部状态”表明对象内部有一个属性来表示状态。 2. “内部状态改变时改变它的行为,对象看起来好像修改了它的类”说明状态改变后对象的行为发生了非常大的变化,不像是同一类的行为。 从目前的分析中似乎无法推导出状态模式的类图结构。 从实际的例子出来,来看看状态模式是如何演进而来。 💡我们有一个文档审批系统,文档有以下状态和转换: 1. 草稿(Draft) → 提交 → 待审批(PendingReview) 2. 待审批 → 批准 → 已发布(Published) 3. 待审批 → 拒绝 → 草稿 4. 已发布 → 撤回 → 草稿 从直觉出发,会使用条件语句实现需求逻辑。 public class Document { private String state = "DRAFT"; // 初始状态为草稿 public void submit(

By 李浩

设计模式之代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。 解析这个定义: 1. “替身”表明在客户端看来,代理类与被代理类是同一类别,对客户端来说看上去没什么区别,依然能够满足诉求。如此可以看出代理类与被代理类来自同一个超类。 2. “控制对这个对象的访问”,能够控制访问,说明前提是能够访问,才能在访问之前做这个限制,即代理持有对真实对象的引用(或能创建它)。 当然可能会质疑,用继承的方式不是也能完成目标吗,用代理类去继承被代理类,然后重写方法,加入控制逻辑。但这违背了"组合优于继承"原则,代理类与被代理类强耦合。 💡我们要开发一个图片查看器,需求如下: 1. 图片加载开销大(从磁盘或网络加载耗时),希望首次显示时才加载(延迟加载)。 2. 某些图片需要权限校验,只有授权用户才能查看。 3. 客户端代码应统一接口,无需关心是直接加载图片还是通过代理。 // 1. 抽象接口(Subject) interface Image { void display(); } // 2. 真实对象(RealSubject)

By 李浩

设计模式之组合模式

组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次关系,使得客户端可以统一处理单个对象和组合对象,而无需关心具体的对象类型。 解析这个定义: 1. “结构型”&“层次关系”,说明此模式针对的对象结构十分典型,对象间的关系有明显的结构特征。 2. “树形结构”,说明此模式就是针对树形结构的对象关系。 3. “部分-整体”,说明组合对象包含了单个对象。 4. “客户端可以统一处理”,客户端可以不关心对象是属于单个对象还是组合对象,表明单个对象和组合对象有相同的超类,而且实现了相同的接口。 在设计业务场景中,有部分整体关系,成树形结构的,有许多,比如: * 文件 vs 文件夹 * 员工 vs 部门 * 评论 vs 回复 从对定义的解析,到树形结构的实现,可以推导出组合模式的结构图。 但,组合模式的关键点,不在于结构图,而在于真正理解这个模式解决的问题,已经为什么要如此设计。 组合模式的核心本质之一,是将递归逻辑从客户端转移到“组合对象”内部。

By 李浩