设计模式之命令模式
命令模式
命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接收,怎么被操作等。命令模式属于行为型模式。
一、命令模式包含的角色
命令模式的通用类图:
接收者角色(Receiver):该类负责具体实施或执行一个请求。
命令角色(Command):定义需要执行的所有命令行为。
具体命令角色(Concrete Command):该类内部维护一个接收者(Receiver),在其执行方法中调用Receiver的相关方法;
请求者角色(Invoker): 接收客户端的命令,并执行命令。
Command的出现就是作为Receiver和Invoker的中间件,解耦了彼此;Invoker是一个具体的实现,等待接收客户端传入命令(即Invoker与客户端耦合),Invoker处于业务逻辑区域,应当是一个稳定的结构。而Receiver是属于业务功能模块,是经常变动的;如果没有Command,则Invoker紧耦合Receiver,一个稳定的结构依赖了一个不稳定的结构,就会导致整个结构都不稳定了。这也就是Command引入的原因:不仅仅解耦了请求与实现,同时稳定(Invoker)依赖稳定(Command),结构还是稳定的。
扩展性增强:
- Receiver 属于底层细节,可以通过更换不同的Receiver达到不同的细节实现
- Command 接口本身就是抽象稳定,本身就具备扩展性;而且由于命令对象本身就具备抽象,如果结构装饰器模式,将更加强大
二、命令模式通用写法
-
命令角色
/** * 命令模式-抽象命令 * * @author zdp * @date 2022-08-21 10:31:23 */ public interface ICommand { /** * 执行命令 */ void execute(); }
-
具体命令角色
/** * 命令模式-具体命令 * * @author zdp * @date 2022-08-21 10:31:33 */ public class ConcreteCommandImpl implements ICommand { /** * 直接创建命令接收者,不暴露给客户端 */ private Receiver receiver = new Receiver(); @Override public void execute() { //执行命令 receiver.doCommand(); } }
-
接收者角色
/** * 命令模式-命令接收者 * * @author zdp * @date 2022-08-21 10:33:45 */ public class Receiver { /** * 命令执行方法 */ public void doCommand(){ System.out.println("命令被执行了...."); } }
-
请求者角色
/** * 命令模式-命令执行者 * * @author zdp * @date 2022-08-21 10:33 */ public class Invoker { private ICommand command; public Invoker(ICommand command) { this.command = command; } /** * 客户端只管执行命令而不需知晓命令是如何进行执行的 */ public void execute(){ command.execute(); } }
-
测试
public class Test { public static void main(String[] args) { ICommand command = new ConcreteCommandImpl(); Invoker invoker = new Invoker(command); invoker.execute(); } }
三、命令模式在业务中的应用举例
需求:在一个视频播放器中存在许多的命令动作,将其动作通过命令模式来进行处理
-
命令角色-视频播放器执行命令
/** * 抽象命令-视频播放器执行命令 * * @author zdp * @date 2022-08-21 10:33 */ public interface IAction { /** * 执行命令 */ void execute(); }
-
具体命令-关闭播放命令
/** * 具体命令-视频播放器-关闭播放命令 * * @author zdp * @date 2022-08-21 10:33 */ public class CloseActionImpl implements IAction { private Player player = new Player(); @Override public void execute() { player.close(); } }
-
具体命令-暂停播放命令
/** * 具体命令-视频播放器-暂停播放命令 * * @author zdp * @date 2022-08-21 10:33 */ public class PauseActionImpl implements IAction { private Player player = new Player(); @Override public void execute() { player.pause(); } }
-
具体命令-倍速播放命令
/** * 具体命令-视频播放器-倍速播放命令 * * @author zdp * @date 2022-08-21 10:33 */ public class SpeedActionImpl implements IAction { private Player player = new Player(); @Override public void execute() { player.speed(); } }
-
具体命令-停止播放命令
/** * 具体命令-视频播放器-停止播放命令 * * @author zdp * @date 2022-08-21 10:33 */ public class StopActionImpl implements IAction { private Player player = new Player(); @Override public void execute() { player.stop(); } }
-
命令接收者-视频播放器
/** * 命令接收者-视频播放器 * * @author zdp * @date 2022-08-21 10:36:41 */ public class Player { public void close(){ System.out.println("视频播放关闭...."); } public void pause(){ System.out.println("视频暂停播放...."); } public void speed(){ System.out.println("视频加速播放...."); } public void stop(){ System.out.println("视频停止播放...."); } }
-
命令执行者-视频播放控制器
/** * 命令执行者-视频播放控制器 * * @author zdp * @date 2022-08-21 10:33 */ @Data public class Controller { /** * 单条命令执行 */ private IAction action; /** * 命令批量执行 */ public List<IAction> actions = new LinkedList<>(); public Controller() { } public Controller(IAction action) { this.action = action; } /** * 执行单条指定的命令 */ public void execute() { action.execute(); } /** * 添加命令 */ public void addAction(IAction action){ actions.add(action); } /** * 批量执行命令,命令宏 */ public void batchExecute() { if(CollectionUtils.isEmpty(actions)){ return; } for (IAction iAction : actions) { iAction.execute(); } actions.clear(); } }
-
测试
public class Test { public static void main(String[] args) { IAction command = new PauseActionImpl(); Controller invoker1 = new Controller(command); invoker1.execute(); command = new StopActionImpl(); Controller invoker2 = new Controller(command); invoker2.execute(); System.out.println("=======================批处理,命令宏"); Controller batchInvoker = new Controller(); batchInvoker.addAction(new PauseActionImpl()); batchInvoker.addAction(new SpeedActionImpl()); batchInvoker.addAction(new StopActionImpl()); batchInvoker.addAction(new CloseActionImpl()); batchInvoker.batchExecute(); } }
四、命令模式在源码中的体现
JDK
的Runnable接口,实际上Runnable就相当于就是命令的抽象,只要是实现了Runnable接口的类都会被认为是一个线程。
public interface Runnable {
public abstract void run();
}
在调用线程的start()方法之后,启动的线程就有资格去抢CPU资源,而不需要我们自己编写获得CPU资源的逻辑。而线程抢到CPU资源后,就会执行run()方法中的内容,Runnable接口就实现了用户请求和CPU执行之间的解耦。
五、命令模式的优缺点
- 优点
- 通过引入中间件(Command),解耦了命令请求与实现
- 扩展性良好,可以很容易的增加新命令
- 支持组合命令,支持命令队列
- 可以在现有命令的基础上,增加额外功能,结合装饰器模式更加强大
- 缺点
- 具体的命令类可能会过多
- 增加了系统的难度,理解起来会稍复杂