设计模式之命令模式

2019-12-19 19:06:27 浏览数 (1)

简介

命令模式(Command Pattern)属于设计模式中的行为型模式。命令模式实现了施令者与具体命令的解耦,并且可以实现撤销等命令相关功能。

定义

命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

命令模式的核心是命令,何为命令?其实命令在日常用于中叫请求,调用一个类的方法,其实就是给类发一个命令让它去执行这个方法,命令模式就是将原本的函数调用封装到类中,由方法级别提升到类级别,从而实现请求者与执行者的解耦,并且可以提供更多的功能,譬如撤销,记录日志等。

角色

命令模式的角色有抽象命令角色,具体命令角色,命令接收者角色及命令调用者角色。

抽象命令:定义命令的通用接口,用于提供给调用者统一的调用API 具体命令:实现具体的命令执行逻辑 命令接收者:真正的逻辑实现地方,(命令提供给调用方一个统一界面,真正逻辑并不在命令里实现,如果没有命令接收者,具体命令就需要充当命令接收者的角色) 调用者:请求的发起方

其实应该还有一个协调者角色,它负责初始化具体的命令,设置到调用者中,不过这个一般在main 函数中实现,不列到此处。

模式说明

既然是将对象请求封装为命令,那么所有的代码其实都可以封装成命令模式,区别只是有没有必要。在具体实践中,用户界面相关使用最多,因为命令模式支持的撤销、日志、记录等功能都特别符合用户需求。

在浏览器或手机APP 中,都会提供返回功能,用户只需要点击返回按钮就可以返回到上一层,对于开发人员来说,这个功能浏览器和手机都已经提供了,只需要调用一个 back 就可以。如果让我们来实现,就可以使用命令模式来将 back 映射到撤销功能。

代码语言:javascript复制
/**
  * 在浏览器中是URL跳转
  * 在手机中是 Activity 跳转
  */
public interface Command<T> {
    void execute(T param);
    void back();
}

public class UrlCommand implements Command<String> {
    private WindowHanlder windowHandler;
    private Window preWindow;
    private UrlReceiver receiver;
    public UrlCommand(UrlReceiver rec) {
        this.receiver = rec;
        
    }
    public void execute(String url) {
        // 保存当前窗口
        this.preWindow = windowHandler.getWindow();
        rec.parseAndRend(url);
    }
    
    public void back() {
        // 将原窗口再设置回去
        windowHandler.setWindow(preWindow);
    }
}

/**
  * url 具体执行
  */
public class UrlReceiver {
    public void parseAndRend(String url) {
        // 获取url 对应内容
        String content = parse(url);
        // 渲染到窗口中
        rend(content);
    }
    
    private void rend(String content) {
        // 渲染窗口
    }
}

public class User {
    public static void main(String[] args) {
        // 在浏览器里用户点击 url
        String url = "";
        UrlCommand command = new UrlCommand(new UrlReceiver());
        command.execute(url);
        
        // 用户点击返回
        command.back();
    }
}

此处 Command 只能支持单个命令的执行撤销,我们可以写一个 宏命令来封装一串命令执行后的撤销功能

代码语言:javascript复制
public class MacroUrlCommand implements Command<String> {
    private Stack<Command> commands = new Stack<>();
    private UrlReceiver receiver;
    public MacroUrlCommand(UrlReceiver rec) {
        this.receiver = rec;
        
    } 
    public void execute(String url) {
        UrlCommand command = new UrlCommand(receiver);
        commands.push(command);
        command.execute(url);
    }
    
    public void back() {
        if(CollectionUtils.isEmpty(commands)) {
            return;
        }
        
        Command last = commands.pop();
        last.back();
    }
}

使用场景

  • 请求方与接收方解耦,请求方不需要知道接收方的细节,就像我们使用遥控器调节电视,不需要知道原理,只需要按一下按钮
  • 统一用户界面。开关电灯和开关电脑可以使用相同的用户界面,用户只需要按一下按钮就可以打开电灯或电脑,而不需要重新学习。命令模式通过抽象请求,来实现统一的命令界面
  • 有撤销等功能要求的时候。命令模式定义中就含有此类功能,如果用户要求支持撤销,命令模式是天然的选择
  • 需要支持 redo 和 undo。 数据库等软件在执行插入删除等命令前会将命令记录下来,以便系统崩溃时可以重建。也可以在执行命令后撤销对系统对修改

优点

  • 请求者与实现者解耦,两个可以独立变化
  • 命令可以结合起来变成组合命令(类似装饰模式)
  • 命令作为一个执行单元,支持 redo 和 undo(为避免命令过重,可以与备忘录模式结合)
  • 简单的批处理。可以对系统发一系列命令,系统顺序处理,降低通信成本

缺点

  • 具体命令很容易膨胀,导致系统越来越难以维护
  • 命令对原本直接调用加了一个间接层,增加了系统复杂度,使得理解系统更加困难

模式辨析

Callback 模式:告知某人操作已经完成,操作结果是什么

观察者模式:通知 n(n>=0) 个观察者某个事件发生

命令模式: 封装对对象的调用,使得命令有一个统一界面,相同命令可以相互转换

0 人点赞