小明在公司负责开发音乐应用,用户听音乐的时候可以点击播放、暂停、上一首、下一首,可以循环播放。
小明是这样写的:
/**
* 循环播放的音乐播放器实例
*/
public class PlayerManager {
private List<String> mList = new ArrayList();
private int pos = 0;
public PlayerManager() {
}
public int getPos() {
return pos;
}
public List<String> getList() {
return mList;
}
public void setList(List<String> list) {
mList = list;
}
public void start() {
System.out.println("开始播放:" + mList.get(pos));
}
public void stop() {
System.out.println("暂停播放:" + mList.get(pos));
}
public void next() {
System.out.println("播放下一首==============================>");
if (mList.size() == (pos + 1)) {
pos = 0;
} else {
pos++;
}
start();
}
public void pre() {
System.out.println("播放上一首==============================>");
if (pos == 0) {
pos = mList.size() - 1;
} else {
pos--;
}
start();
}
}
public void test() {
PlayerManager player = new PlayerManager();
List<String> mList = new ArrayList();
mList.add("1.mp3");
mList.add("2.mp3");
mList.add("3.mp3");
player.setList(mList);
player.start();
player.stop();
player.next();
player.next();
player.next();
player.next();
player.pre();
player.pre();
player.pre();
player.pre();
}
输出结果
开始播放:1.mp3
暂停播放:1.mp3
播放下一首==============================>
开始播放:2.mp3
播放下一首==============================>
开始播放:3.mp3
播放下一首==============================>
开始播放:1.mp3
播放下一首==============================>
开始播放:2.mp3
播放上一首==============================>
开始播放:1.mp3
播放上一首==============================>
开始播放:3.mp3
播放上一首==============================>
开始播放:2.mp3
播放上一首==============================>
开始播放:1.mp3
小明的领导一看,说这样写不行,如果业务逻辑更复杂了,比如增加顺序播放、随机播放、单首循环播放,或者在播放前要判断是否会员,是否购买该歌曲,岂不是要修改一直Player类?Player类越来越臃肿。Player应该只负责播放器的功能,不应该掺杂业务逻辑,不符合单一职责。Player的扩展性也很低,如果要增加新的播放器命令,比如音效、倍速播放、定时关闭等等功能,只能在原来的基础上去增加。
领导建议使用命令模式,播放器负责播放逻辑,用户负责请求,命令负责执行逻辑。
改造时,增加一个业务逻辑:当播放某首没有权限的歌时,自动切换到下一首歌。
播放器只负责播放器的功能,比如播放和暂停。
/**
* 音乐播放器实例
*/
public class Player {
public Player() {
}
public void start(String url) {
System.out.println("开始播放:" + url);
}
public void stop(String url ) {
System.out.println("暂停播放:" + url);
}
}
抽象出命令类
public interface Command {
/**
* 命令执行方法
*/
void execute();
}
具体命令
public class StartCommand implements Command {
private Player mPlayer;
public StartCommand(Player player) {
mPlayer = player;
}
@Override
public void execute(String url) {
mPlayer.start(url);
}
}
public class StopCommand implements Command {
private Player mPlayer;
public StopCommand(Player player) {
mPlayer = player;
}
@Override
public void execute(String url) {
mPlayer.stop(url);
}
}
Buttons通过命令对播放器进行控制,写一些业务逻辑
public class Buttons {
private List<String> mList = new ArrayList();
private int pos = 0;
private StartCommand mStartCommand;
private StopCommand mStopCommand;
public void setStartCommand(StartCommand startCommand) {
mStartCommand = startCommand;
}
public void setStopCommand(StopCommand stopCommand) {
mStopCommand = stopCommand;
}
public void start() {
String url = mList.get(pos);
//假设3.map没有权限播放
if (url.equals("3.mp3")) {
System.out.println("没有“3.mp3”的播放权限,将播放下一首歌");
//发送播放下一首歌的指令
next();
return;
}
mStartCommand.execute(mList.get(pos));
}
public void stop() {
mStopCommand.execute(mList.get(pos));
}
public void next() {
pos = getNextUrlPos();
start();
}
public void pre() {
pos = getPreUrlPos();
start();
}
public List<String> getList() {
return mList;
}
public void setList(List<String> list) {
mList = list;
}
public int getNextUrlPos() {
int next;
if (mList.size() == (pos + 1)) {
next = 0;
} else {
next = pos + 1;
}
return next;
}
public int getPreUrlPos() {
int next;
if (pos == 0) {
next = mList.size() - 1;
} else {
next = pos - 1;
}
return next;
}
}
开始测试
public void test() {
Player player = new Player();
StartCommand startCommand = new StartCommand(player);
StopCommand stopCommand = new StopCommand(player);
Buttons buttons = new Buttons();
buttons.setStartCommand(startCommand);
buttons.setStopCommand(stopCommand);
List<String> mList = new ArrayList();
mList.add("1.mp3");
mList.add("2.mp3");
mList.add("3.mp3");
buttons.setList(mList);
buttons.start();
buttons.stop();
buttons.next();
buttons.next();
buttons.next();
buttons.pre();
buttons.pre();
buttons.pre();
}
输出结果
开始播放:1.mp3
暂停播放:1.mp3
开始播放:2.mp3
没有“3.mp3”的播放权限,将播放下一首歌
开始播放:1.mp3
开始播放:2.mp3
开始播放:1.mp3
没有“3.mp3”的播放权限,将播放下一首歌
开始播放:1.mp3
没有“3.mp3”的播放权限,将播放下一首歌
开始播放:1.mp3
可以看到,播放器类只专注于自己的播放和暂停功能即可。Buttons不直接调用播放器的方法,而是通过具体命令去调用,这样无论播放器怎么改动,只会影响命令,不会影响Buttons。Buttons负责具体的业务逻辑,如果有一天,需要另外做一套有不同逻辑的播放界面,那么只需要再另外创建一个Buttons,再写一套业务逻辑即可。如果不使用命令模式,如果需要改动业务逻辑,岂不是要在Player上修改?Player将逻辑越来越复杂,让人难以理解。
看一下命令模式的UML图
Receiver,接收者:执行具体逻辑的底层代码,任何一个类都可以成为一个接收者,封装了具体操作逻辑的方法称为行动方法。
Command,抽象命令:定义所有具体命令类的抽象接口。
ConcreteCommand,具体命令:实现了Command接口,负责执行接收者的方法。
Invoker,请求者:负责调用具体命令
意图:
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
主要解决:
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
假设我们想一次性执行多个命令呢?
public class Controller {
private List<Command> Commands = new ArrayList<>();
public void addCommand(Command Command) {
Commands.add(Command);
}
public void execute(Command Command) {
Command.execute();
}
public void executes() {
for (Command Command:
Commands) {
Command.execute();
}
Commands.clear();
}
}
// 控制条可以执行单挑命令也可以批量执行多条命令
VideoPlayer player = new VideoPlayer();
Controller controller = new Controller();
controller.execute(new PlayCommand(player));
controller.addCommand(new PauseCommand(player));
controller.addCommand(new PlayCommand(player));
controller.addCommand(new StopCommand(player));
controller.addCommand(new SpeedCommand(player));
controller.executes();
假设我们想对命令做一个撤销功能呢?比如键盘打字后,按“Ctrl+Z”可以返回上一个操作
ICommand命令类
public interface ICommand {
void write();
void undo();
}
WriteCommand具体命令类
public class WriteCommand implements ICommand {
public String word;
public Tablet tablet;
public WriteCommand( Tablet tablet,String word) {
this.word = word;
this.tablet = tablet;
}
@Override
public void write() {
tablet.write(word);
}
@Override
public void undo() {
tablet.undo();
}
}
接收者Tablet ,负责具体的写字和撤销逻辑
/**
* 写字板实例
*/
public class Tablet {
private List<String> mList = new ArrayList<>();
public void write(String word) {
mList.add(word);
System.out.println("显示:" + mList.toString());
}
public void undo() {
if (mList.size() > 0) {
mList.remove(mList.size() - 1);
}
System.out.println("显示:" + mList.toString());
}
}
调用者Invoker类,将执行过的命令的都保存起来,方便撤销
public class Invoker {
private List<WriteCommand> mCommands = new ArrayList<>();
public void execute(WriteCommand command) {
mCommands.add(command);
command.write();
}
public void executes(List<WriteCommand> commands) {
mCommands.addAll(commands);
for (ICommand command :
commands) {
command.write();
}
}
public void undo() {
if (mCommands.size() > 0) {
WriteCommand writeCommand = mCommands.get(mCommands.size() - 1);
writeCommand.undo();
}
}
}
测试
public void test5() {
Tablet tablet = new Tablet();
Invoker invoker = new Invoker();
invoker.execute(new WriteCommand(tablet,"a"));//执行一个命令
invoker.execute(new WriteCommand(tablet,"b"));//执行一个命令
invoker.execute(new WriteCommand(tablet,"c"));//执行一个命令
List<WriteCommand> mCommands = new ArrayList<>();
mCommands.add(new WriteCommand(tablet,"d"));
mCommands.add(new WriteCommand(tablet,"e"));
mCommands.add(new WriteCommand(tablet,"f"));
invoker.executes(mCommands);//执行一系列命令
invoker.undo();//撤销
invoker.undo();//撤销
}
输出结果
显示:[a]
显示:[a, b]
显示:[a, b, c]
显示:[a, b, c, d]
显示:[a, b, c, d, e]
显示:[a, b, c, d, e, f]
显示:[a, b, c, d, e]
显示:[a, b, c, d]
在一些需要有撤销功能的应用中,是很适合用命令模式的,比如画图、图片编辑、视频编辑、富文本编辑器。
又比如电商产品,要求程序员将用户在购买完成后,把购买之前经历过的流程,都反馈到后台,方便进行行为分析。比如说“进入首页->搜索商品->打开详情->查看第一页评论->查看第二页评论->查看第三页评论->点击购买->付款成功”,可以知道该用户是很在意评论的。
其实用户的这些行为,是不是就像一个个命令,我们完全可以在购买之前将这些命令记录下来,然后在购买完成时发送到后台,最后清理命令。