命令模式

概述

《设计模式》一书中对于 “命令模式” 的意图描述如下:

将一个请求封装成为一个对象,从而可以使用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作

一般 “命令模式” 的 UML 图如下所示:

command_pattern.png

一般会在以下几种情况下使用命令模式:

  • 需要抽象出待执行的动作以参数化某对象
  • 在不同的时刻指定、排列和执行请求

具体示例

对于一个图形编辑器的界面来讲,对每个按钮或者菜单的点击都需要向当前编辑的文档对象发送一个请求,以完成相关的特殊操作,如:打开、保存、撤销、粘贴等。这一类请求的接受着为文档对象,但是文档对象并不能明确地知道会收到何种请求,同时,对于每个操作按钮或菜单项来讲,它们不能显式地实现相关的功能,因为它们不知道它们本身应该在何处被调用(即不知道所处得到上下文)

为了解决这个问题,我们可以考虑使用命令模式来进行处理,对于每个按钮的操作,我们都将它封装成为一个 “命令对象”,这样对于菜单项来讲就不需要知道当前的上下文,同时能够更加通用地将请求发送给对应的接受者

首先,定义文档对象的相关接口 Document

public interface Document {
    void copyContent(long start, long end); // 复制文档的部分内容
}

我们的编辑器可能需要支持普通文本文档和图形文档的编辑,因此我们需要定义对应的子类型:

// 普通文本文档类型
public interface TextDocument extends Document {}

// 图片类型文档
public interface ImageDocument extends Document {}

定义我们的编辑器的命令接受者对象 Receiver

public interface Receciver {
    void action();
    void createDocument(); // 从输入流中读取并构造并替换当前文档对象
}

定义操作命令的统一接口 Command

public interface Command {
    void execute();
}

针对打开文档和复制文档内容的具体命令 OpenDocumentCommandCopyContentCommand

public class OpenDocumentCommand implements Command {
    private final Receiver receiver;
    
    public OpenDocumentCommand(Receiver receiver) {
        this.receiver = receiver;
    }
    
    @Override
    public void execute() {
        receiver.action();
        receiver.createDocument(readFile(selectFile()));
    }
}

public class CopyContentCommand implements Command {
    private final Receiver receiver;
    private final Document doc; // 复制的操作只能针对已经打开的文档
    
    public CopyContentCommand(Receiver receiver, Document doc) {
        this.receiver = receiver;
        this.doc = doc;
    }
    
    @Override
    public void execute() {
        receiver.action();
        doc.copyContent(locateMouseStart(), locateMouseEnd());
    }
}

当选择一个按钮点击时,只需要将其关联的命令发送给调用者(在这里是文件编辑器对象)即可,一打开文档的操作为例:

public class OpenCodumentMenuItem {
    
    public void onClick() {
        Receiver receiver = obtainReceiver();
        OpenDocumentCommand cmd = new OpenDocumentCommand(receiver);
        cmd.execute();
    }
}

这样实现可以支持后续的扩展,比如撤销、重做等功能,而不需要逐一地修改每个菜单项的具体实现

总结

一般对于组合的组件来讲,同样可以命令模式来解除某些不必要的耦合,比如将多个命令组合成一个大命令,同时结合 “组合模式” 来处理多个组件之间的协调关系



参考:

[1] 《设计模式—可复用面向对象基础》

posted @ 2023-04-01 11:27  FatalFlower  阅读(15)  评论(0编辑  收藏  举报