个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道
如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充
前言
生活中,一件事情的请求者和执行者不一定是同一个人。比如我们申请售后的时候,将需要的信息等告知售后,然后由售后来安排处理,我们可以去做别的事情了;电视遥控器向电视发送命令,电视来执行;老板给我们分配任务等等。。。
好处是我们不需要将请求者与执行者建立绑定关系,也不需要占用请求者的时间。
在开发中,我们也会需要解除请求者和执行者之间的耦合,一方面利于扩展,一方面使得两者不必同步运作。
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
1.介绍
使用目的:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开
使用时机:需要将方法的请求者与方法的实现者解耦,进而让两者之间通过命令对象进行沟通,方便将命令对象进行储存、传递、调用、增加与管理。
解决问题:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的
实现方法:请求者通过调用者,再由调用者来调用接受者执行命令。
应用实例:
- 下载管理器,请求者将请求发送给管理器,由管理器来执行并管理下载任务
- 命令池,请求者将命令组装好后传入命令池,由命令池执行并管理下载任务
优点:
- 降低了系统耦合度
- 新的命令可以很容易添加到系统中
- 释放了请求者,使其不必等待执行结果
缺点:使用命令模式可能会导致某些系统有过多的具体命令类
2.结构
命令模式包含以下主要角色
- 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
- 具体命令(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
- 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
- 客户端调用
invoker
invoker
持有Commond
对象,并执行execute()
命令ConcreteCommand
需要实现Commond
接口,并持有Receiver
对象,在execute()
方法中调用Receiver
则需要定义相关的方法,以供ConcreteCommand
调用
3.实现步骤
-
定义接收者
class ReceiverA { public void action() { System.out.println("执行方法A"); } } class ReceiverB { public void action() { System.out.println("执行方法B"); } }
接收者里面负责定义具体需要执行的相关方法
-
定义抽象命令类
interface Command { void execute(); }
用于规范命令类提供的方法接口
-
定义具体命令类
class ConcreteCommandA implements Command { private ReceiverA receiver; public ConcreteCommandA() { receiver = new ReceiverA(); } @Override public void execute() { receiver.action(); } } class ConcreteCommandB implements Command { private ReceiverB receiver; public ConcreteCommandB() { receiver = new ReceiverB(); } @Override public void execute() { receiver.action(); } }
此类为核心,需要在此处实现抽象命令类,并持有接收者,并定义命令执行的逻辑
-
定义调用者
class Invoker { private Command command; public void setCommand(Command command) { this.command = command; } public void call() { if (null != command) { command.execute(); } } }
持有命令对象,并提供设置和执行命令的方法
完整代码
package com.company.test.command;
class ReceiverA {
public void action() {
System.out.println("执行方法A");
}
}
class ReceiverB {
public void action() {
System.out.println("执行方法B");
}
}
interface Command {
void execute();
}
class ConcreteCommandA implements Command {
private ReceiverA receiver;
public ConcreteCommandA() {
receiver = new ReceiverA();
}
@Override
public void execute() {
receiver.action();
}
}
class ConcreteCommandB implements Command {
private ReceiverB receiver;
public ConcreteCommandB() {
receiver = new ReceiverB();
}
@Override
public void execute() {
receiver.action();
}
}
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void call() {
if (null != command) {
command.execute();
}
}
}
public class CommandTest {
public static void main(String[] args) {
Invoker invoker = new Invoker();
Command commandA = new ConcreteCommandA();
invoker.setCommand(commandA);
invoker.call();
Command commandB = new ConcreteCommandB();
invoker.setCommand(commandB);
invoker.call();
}
}
运行结果
4.扩展实战
目标需求如下:
-
设计一个命令池,被多个命令类共享
-
客户端调用请求者的方法,由请求者组装命令,并交付给命令池
-
命令池会按顺序执行接收到的命令
- 命令按照目标不同进行分组
- 同一组命令按照先后执行,如果某个命令执行时间超过2s则不再等待,执行下一条命令
- 每条命令均需要回调,即便超时
实际可能的业务场景如下
项目需要向多个设备下发命令,通过同一个的命令池管理
对于同一个设备的命令需要按顺序执行,若某条命令完成或者超时则执行下一条,但每条命令均必须有回调
因此不考虑超时的情况下,同一设备的命令为堵塞队列
对于不同设备,之间的命令不能堵塞,不能相互影响
系统内的命令只负责调用设备,具体设备业务由设备自己执行
测试客户端代码:
public class CommandPoolTest {
public static void main(String[] args) {
CommandPool commandPool = new CommandPool();
CommandInterface command_1 = new CommandImpl(commandPool);
command_1.doAB();
command_1.doABC();
while(true){}
}
}
- 一共下发两条命令,分别是
AB
和ABC
,那么全部子命令应该是ABABC
- 系统中任务等待时间为1秒,超时则执行下一条命令
- 代码中命令
A
和C
都是瞬时任务,B
为持续3秒的任务,因此B
会超时 - 系统中我们添加了两个目标,因此每个命令都会发送到这两个目标
部分运行结果:
- 第一条命令
A
正常执行,并得到反馈 - 第二条命令
B
下发,但未得到反馈(超时) - 第三条命令
A
下发,并得到反馈
完整的运行结果中,下发命令的顺序是ABABC
,而收到反馈的顺序是AACBB
,因为两次B
都是超时完成
完整Demo地址:https://gitee.com/echo_ye/practice/tree/master/src/main/java/com/company/commandPool
后记
设计模式只是提供了一种解决某个问题的思路,在实际开发中往往需要多种设计模式协同使用,并且需要根据需求扩展和变型
比如这个demo,初略看了一下,使用了命令模式、享元模式、代理模式、单例模式等,还有我也分不清楚了。。。
总之,最终目的就是实现我们的需求,而设计模式则提供了解决的思路
作者:Echo_Ye
WX:Echo_YeZ
Email :echo_yezi@qq.com
个人站点:在搭了在搭了。。。(右键 - 新建文件夹)