这是我参与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