1. 命令设计模式介绍
将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
2. 命令模式使用场景
- 整个调用过程比较复杂,或者存在多处这种调用。这时,使用Command类对该调用加以封装,便于功能的再利用。
- 调用前后需要对调用参数进行某些处理。
- 调用前后需要进行某些额外处理,比如日志,缓存,记录历史操作等等。
3. 命令模式的UML类图
4. 命令模式的简单实现
通用模式代码:
- 接收者类:
public class Receiver {
/**
* 真正执行具体命令逻辑的方法
*/
public void action() {
System.out.println("执行具体操作");
}
}
- 抽象命令接口:
public interface Command {
/**
* 执行具体操作的命令
*/
void execute();
}
- 具体命令类:
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
//调用接收者相关方法来执行具体逻辑
receiver.action();
}
}
- 请求者类:
public class Invoker {
private Command command;//只有一个对相应命令对象引用
public Invoker(Command command) {
this.command = command;
}
public void action() {
//调用具体命令对象的相关方法,执行具体命令
command.execute();
}
}
- 客户端类:
public class Client {
public static void main(String[] args) {
//构造一个接收者对象
Receiver receiver = new Receiver();
//根据接收者对象构造一个命令对象
ConcreteCommand concreteCommand = new ConcreteCommand(receiver);
//根据具体的对象构造请求者对象。
Invoker invoker = new Invoker(concreteCommand);
//执行请求方法
invoker.action();
}
}
下面简单说下以上各个类的作用:
- Receiver: 接收者角色, 其实就是执行具体逻辑的角色。
- Command: 命令角色, 定义所有具体命令的抽象接口。
- ConcreteCommand: 具体命令角色, 该类实现了Command接口。在execute方法中调用接收者角色的相关方法,在接收者和命令执行的具体行为之间加以弱耦合。
- Invoker: 请求角色。该类的职责就是调用命令对象执行具体的请求。
- Client: 客户端角色。
以上是一个通用模式代码,下面说一个具体情景。
比如我们日常生活中的开关灯泡操作。
(1)首先得有一个灯泡类:Bulb
public class Bulb {
//开灯
public void lightUp() {
System.out.println("开灯");
}
//关灯
public void lightOff() {
System.out.println("关灯");
}
}
Bulb类是整个命令模式中唯一处理具体代码逻辑的地方,其他的类都是直接或者间接地调用该类的方法,这个就是接收者的角色,处理具体的逻辑。
(2) 定义命令抽象者,定义执行方法。
public interface Command {
/**
* 命令执行方法
*/
void execute();
}
(3) 具体命令者,开关灯的命令
开灯:
public class LightUpCommand implements Command {
//持有一个灯泡的引用
private Bulb bulb;
public LightUpCommand(Bulb bulb) {
this.bulb = bulb;
}
@Override
public void execute() {
bulb.lightUp();
}
}
关灯:
public class LightOffCommand implements Command {
//持有一个灯泡的引用
private Bulb bulb;
public LightOffCommand(Bulb bulb) {
this.bulb = bulb;
}
@Override
public void execute() {
bulb.lightOff();
}
}
(4) 命令由开关发起
public class Switch {
private LightUpCommand lightUpCommand;
private LightOffCommand lightOffCommand;
/**
* 设置开灯命令
*
* @param lightUpCommand
*/
public void setLightUpCommand(LightUpCommand lightUpCommand) {
this.lightUpCommand = lightUpCommand;
}
/**
* 设置关灯命令
*
* @param lightOffCommand
*/
public void setLightOffCommand(LightOffCommand lightOffCommand) {
this.lightOffCommand = lightOffCommand;
}
/**
* 开灯
*/
public void lightUp() {
lightUpCommand.execute();
}
/**
* 关灯
*/
public void lightOff() {
lightOffCommand.execute();
}
}
(5) 客户端调用:
public class Person {
public static void main(String[] args) {
//1.首先得有一个灯泡
Bulb bulb = new Bulb();
//2.构造两种命令:开、关灯
LightUpCommand lightUpCommand = new LightUpCommand(bulb);
LightOffCommand lightOffCommand = new LightOffCommand(bulb);
//3.设置开关不同状态的执行命令
Switch mSwitch = new Switch();
mSwitch.setLightUpCommand(lightUpCommand);
mSwitch.setLightOffCommand(lightOffCommand);
//4.开关操作
mSwitch.lightUp();
mSwitch.lightOff();
}
}
其实上面调用了这么多,可以用以下代码来代替:
bulb.lightUp();
bulb.lightOff();
直接调用Bulb对象即可。
既然直接调用即可,为什么要调用这么复杂呢?
因为设计模式模式有一条重要的原则: 对修改关闭,对扩展开放。如果我们直接操作对象,就可能会不小心修改错误,而这些错误我们恰好没有发现。此时就用到了命令模式,间接调用。
另一个好处是,在Switch类中,我们可以使用一种存储结构来存储执行过的命令对象,作为日志输出。
5. 命令模式在Android源码中
(1)Android 的事件机制中底层逻辑对事件的转发处理
Android 的事件机制中底层逻辑对事件的转发处理,Android的每一种事件在屏幕上产生后,都经由底层逻辑将其转换为一个NotifyArgs对象,相当于一个命令抽象者。
NotifyKeyArgs等子类则相当于一个具体命令者。
请求者角色由InputDispatcher承担,具体的事件名来者将事件转发给InputDispatcher,并由其来封装具体的事件操作。
(2) Android在对应用程序包管理的部分也有对命令模式应用的体现
- PackageManagerService是Android系统的Service之一,主要功能在于实现对应用包的解析,管理,卸载。对于这三个操作,分别封装为HandlerParams的3个具体子类InstallParams、MoveParams和MeasureParams。结合命令模式,HandlerParams可以看成是一个抽象命令模式。
6. 命令模式在Android开发中
在Android开发中,当我们开启一个线程时,我们经常写到如下的代码:
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
其实这就是一个典型的命令模式。如果我们写成如下的形式:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*new Thread(new Runnable() {
@Override
public void run() {
}
}).start();*/
//1.构造一个接收者对象
Receiver receiver = new Receiver();
//2. 根据接收者对象构造ChildRunnable
ChildRunnable childRunnable = new ChildRunnable(receiver);
//3.根据ChildRunnable构造一个子线程对象
Thread childThread = new Thread(childRunnable);
//4.执行请求方法
childThread.start();
}
//ChildRunnable是命令角色,Runable是命令角色
class ChildRunnable implements Runnable {
private Receiver receiver;
public ChildRunnable(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void run() {
receiver.receive();
}
}
//接收者
class Receiver {
public void receive() {
System.out.println("接收到了");
}
}
}
由上述我们不难发现:
- Receiver是接收者角色
- Runnable 是命令角色
- ChildRunnable 是具体命令角色
- Thread是请求角色
- MainActivity是客户端角色
7. 总结
优点:
- 命令模式的封装性很好。每个命令都被封装起来了,对于客户端来说,想要什么功能,直接去调用即可,而无需知道命令具体是怎么执行的。
- 命令模式的扩展性很好。比如我们想要实现一个剪切的功能,目前已有的功能有复制和删除。所以我们只需要在命令类,对接收者类中的“复制”和“删除”功能进行封装,就完成了“剪切”的功能,而无需在接收者类增加新的代码。
缺点:
- 如果是简单的命令,实现起来很简单的恶化,使用命令模式就会显得繁杂,需要一个命令类去封装。