把书读薄 | 《设计模式之美》设计模式与范式(行为型-命令模式)

这是我参与8月更文挑战的第8天,活动详情查看: 8月更文挑战

0x0、引言

?周五又到,坐等周末,继续啃设计模式之美,本文对应设计模式与范式:行为型(71),命令模式 (Command Pattern),同样是工作中不怎么常用的模式,了解下即可~

Tips:二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。


0x1、定义

原始定义

将一个请求(命令)封装成一个对象,从而让我们可以使用 不同的请求参数化其他对象(依赖注入),且支持请求的排队执行、记录日志、撤销等功能(附加控制)。

落实到编码实现,命令模式用的最核心的实现手段就是:将函数封装成对象以便传递,当你所用的编程语言不支持函数作为参数传递,就可以考虑用下它了。把函数封装成对象,还可以存储控制执行,这也是命令模式的优点~

0x2、写个简单例子

用命令模式写个音乐播放控制的例子吧,先是业务实体:

public class Music {
    private String name;
    private String url;

    public Music(String name, String url) {
        this.name = name;
        this.url = url;
    }

    public String getName() { return name; }
    public String getUrl() { return url; }
}
复制代码

接着到 抽象命令类 → 声明要做的操作:

public interface ICommand {
    void execute();
}
复制代码

再接着到 抽象接收者 → 声明要执行的命令,同时提供给客户端使用:

public interface IPlayer {
    void setPlayList(List<Music> musics);
    void play();
    void play(int cursor);
    void next();
    void pre();
    void pause();
}
复制代码

紧接着到 具体命令类 → 实现抽象命令类,存储一个接收者,执行调用具体命令时,委托给接收者执行具体方法:

public class SetPlayListCommand implements ICommand {
    private final IPlayer player;
    private List<Music> musics;
    
    public SetPlayListCommand(IPlayer player) { this.player = player; }
    
    @Override public void execute() { player.setPlayList(musics); }
    
    public void setPlayList(List<Music> musics) { this.musics = musics; }
}

public class PlayCommand implements ICommand {
    private final IPlayer player;

    public PlayCommand(IPlayer player) { this.player = player; }

    @Override public void execute() { player.play(); }
}

public class NextCommand implements ICommand {
    private final IPlayer player;

    public NextCommand(IPlayer player) { this.player = player; }

    @Override public void execute() { player.next(); }
}

// 剩余两个命令类似,调用不同的player方法而已...
复制代码

再接着到 具体接收者 → 实现抽象接收者,接收命令并执行真实代码逻辑:

public class MusicPlayer implements IPlayer {
    private int cursor = -10000; // 当前播放游标
    private int pauseCursor = -10000; // 当前暂停游标
    private final List<Music> musics = new ArrayList<>();

    @Override
    public void setPlayList(List<Music> musics) {
        this.musics.clear();
        if(musics == null || musics.isEmpty()) {
            System.out.println("设置播放列表不能为空!");
        } else {
            this.musics.addAll(musics);
            this.cursor = -10000;
            System.out.println("列表设置成功,当前曲目:");
            for(Music music: this.musics) {
                System.out.println("【" + music.getName() +"】");
            }
        }
    }

    @Override
    public void play() {
        if(musics.size() == 0) {
            System.out.println("当前播放列表为空,请先设置播放列表");
        } else {
            if(cursor == -10000) {
                cursor = 0;
                System.out.println("开始播放:" + musics.get(0).getName());
            } else {
                if(cursor < musics.size()) {
                    if(cursor == pauseCursor) {
                        System.out.println("继续播放:" + musics.get(cursor).getName());
                        pauseCursor = -10000;
                    } else {
                        if(cursor < 0) {
                            cursor = musics.size() - 1;
                        }
                        System.out.println("开始播放:" + musics.get(cursor).getName());
                    }
                } else {
                    System.out.println("播放列表歌曲已经播放完毕,重头开始播放");
                    cursor = 0;
                    System.out.println("开始播放:" + musics.get(0).getName());
                }
            }
        }
    }

    @Override
    public void next() {
        cursor++;
        play();
    }

    @Override
    public void pre() {
        cursor--;
        play();
    }

    @Override
    public void pause() {
        pauseCursor = cursor;
        System.out.println("暂停播放");
    }
}
复制代码

最后是 调用者 → 客户端通过与调用者交互来操作不同的命令对象。

public class Client {
    public static void main(String[] args) {
        // 实例化播放列表
        List<Music> musics = new ArrayList<>();
        musics.add(new Music("白雪公主", ""));
        musics.add(new Music("青蛙的愿望", ""));
        musics.add(new Music("驴和马", ""));
        musics.add(new Music("小青蛙的烦恼", ""));
        musics.add(new Music("三字经", ""));

        // 实例化接收者
        IPlayer musicPlayer = new MusicPlayer();

        // 实例化调用者,并传入具体命令实例
        Invoker invoker = new Invoker();
        invoker.setSetPlayListCommand(new SetPlayListCommand(musicPlayer));
        invoker.setPlayCommand(new PlayCommand(musicPlayer));
        invoker.setNextCommand(new NextCommand(musicPlayer));
        invoker.setPreCommand(new PreCommand(musicPlayer));
        invoker.setPauseCommand(new PauseCommand(musicPlayer));

        // 测试调用
        invoker.play();
        invoker.setPlayList(null);
        invoker.setPlayList(musics);
        invoker.play();
        invoker.next();
        invoker.pre();
        invoker.pre();
        invoker.pause();
        invoker.play();
        invoker.next();
        invoker.next();
        invoker.next();
        invoker.next();
        invoker.next();
    }
}
复制代码

代码运行结果如下

代码很浅显易懂,涉及到的角色功能也有说,就不再复述了:

抽象命令、具体命令、抽象接收者、具体接收者、调用者

直接带出UML类图、使用场景和优缺点~

使用场景

  • 不支持函数传递的编程语言,利用命令模式可以把函数当成对象使用;
  • 请求调用者和接收者 解耦,不直接交互,调用者无需知道接收者是谁及如何操作;
  • 围绕命令维度构建功能、自由组合相关命令、统计跟踪行为操作;

优点

  • 更松散的耦合,请求者无需知道执行者是谁,如何执行指令。
  • 更动态的控制,将请求封装,可以动态进行参数化,队列化,日志化等操作。
  • 命令可以复合,即一个命令可以由多个命令组合而成,又叫宏命令
  • 更好的扩展性,因为命令发起者与执行者解耦,扩展新命令,只需实现新的命令对象;
  • 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案

缺点

  • 不同接收者需要实现重复命令;
  • 命令涉及对象变化时,可能导致不同的结果;
  • 新增命令,对应的接收者都要新增命令实现,顺带也会影响接收者实现,不好维护;

以上内容就是本节的全部内容,谢谢~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享