命令模式
简介
Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式 解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接收,怎样被操作以及是否被执行····
软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,因为这样的实现简单明了。但紧耦合关系缺乏扩展性,在某些场合中,当需要为行为进行记录,撤销或重做等处理时,只能修改源码。而 命令模式 通过为请求与实现间引入一个抽象命令接口,解耦了请求与实现,并且中间件是抽象的,它可以有不同的子类实现,因此其具备扩展性。
命令模式 本质:解耦命令请求与处理
主要解决
当系统的某项操作具备命令语义时,且命令实现不稳定(变化),那么可以通过 命令模式 解耦请求与实现,利用抽象命令接口使请求方代码架构稳定,封装接收方具体命令实现细节。接收方与抽象命令接口呈现弱耦合(内部方法无需一致),具备良好的扩展性。
优缺点
优点
- 通过引入中间件(抽象接口),解耦了命令请求与实现;
- 扩展性良好,可以很容易地增加新命令;
- 支持组合命令,支持命令队列;
- 可以在现有命令的基础上,增加额外功能(比如日志记录···,结合 装饰器模式 更酸爽);
缺点
- 具体命令类可能过多;
- 命令模式 的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构,解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难(不过这也是设计模式带来的一个通病,抽象必然会引入额外类型;抽象肯定比紧密难理解);
使用场景
- 现实语义中具备 ”命令“ 的操作(如命令菜单,shell命令···);
- 需要将请求与实现解耦;
- 需要支持命令的撤销(Undo)操作和恢复(Redo)操作;
- 需要支持命令组合操作(宏命令);
模式讲解
首先看下 命令模式 的通用 UML 类图:
命令模式从 UML 类图中,我们可以看到,命令模式 主要包含四种角色:
- 接收者角色(Receiver):该类负责具体实施或执行一个请求;
- 命令角色(Command):定义需要执行的所有命令行为;
-
具体命令角色(ConcreteCommand):该类内部维护一个 接收者(Receiver),在其
execut
方法中调用 Receiver 的相关方法; - 请求者角色(Invoker):接收客户端的命令,并执行命令;
注:从 命令模式 的 UML 类图中,其实可以很清晰地看出:Command
的出现就是作为Receiver
和Invoker
的中间件,解耦了彼此。而之所以引入Command
中间件,我觉得是以下两方面原因:
-
解耦请求与实现:即解耦了
Invoker
和Receiver
,因为在 UML 类图中,Invoker
是一个具体的实现,等待接收客户端传入命令(即Invoker
与客户端耦合),Invoker
处于业务逻辑区域,应当是一个稳定的结构。而Receiver
是属于业务功能模块,是经常变动的;如果没有Command
,则Invoker
紧耦合Receiver
,一个稳定的结构依赖了一个不稳定的结构,就会导致整个结构都不稳定了。这也就是Command
引入的原因:不仅仅是解耦请求与实现,同时稳定(Invoker
)依赖稳定(Command
),结构还是稳定的; -
扩展性增强:扩展性体现在两个方面:1.
Receiver
属于底层细节,可以通过更换不同的Receiver
达到不同的细节实现;2.Command
接口本身就是抽象的,本身就具备扩展性;而且由于命令对象本身就具备抽象,如果结合 装饰器模式,功能扩展简直如鱼得水。
以下是 命令模式 的通用代码:
class Client {
public static void main(String[] args) {
ICommand cmd = new ConcreteCommand();
Invoker invoker = new Invoker(cmd);
invoker.action();
}
//接收者
static class Receiver {
public void action() {
System.out.println("执行具体操作");
}
}
//抽象命令接口
interface ICommand {
void execute();
}
//具体命令
static class ConcreteCommand implements ICommand {
// 直接创建接收者,不暴露给客户端
private Receiver mReceiver = new Receiver();
@Override
public void execute() {
this.mReceiver.action();
}
}
//请求者
static class Invoker {
private ICommand mCmd;
private Invoker(ICommand cmd) {
this.mCmd = cmd;
}
public void action() {
this.mCmd.execute();
}
}
}
注:在一个系统中,不同的命令对应不同的请求,也就是说无法把请求抽象化,因此 命令模式 中的Receiver
是具体实现;但是如果在某一个模块中,可以对Receiver
进行抽象,其实这就变相使用到了 桥接模式(Command
类具备两个变化的维度:Command
和Receiver
),这样子的扩展性会更加优秀。
举个例子
例子:假如现有我们有一个遥控器,可以控制风扇的风力大小,分为大,中,小,关闭四个程度,请使用程序进行实现。
分析:上面的例子涉及两个物体:遥控器和风扇,直接的思路就是遥控器紧耦合风扇,然后遥控器内部暴露控制风扇风力等级接口。但是遥控器后续可能还可以对其他电器设备(如空调等)进行控制,因此有必要解耦遥控器和具体电器设备的紧密联系。对遥控器进行操作,相当于发出一个指令(命令),让对应的电器设备进行工作,那么 命令模式 是非常切合这个场景的。
具体代码如下:
class Client {
public static void main(String[] args) {
RemoteController remote = new RemoteController();
Fan fan = new Fan();
ICommand cmd = new TurnMinCommand(fan);
remote.action(cmd);
cmd = new TurnMidCommand(fan);
remote.action(cmd);
cmd = new TurnMaxCommand(fan);
remote.action(cmd);
cmd = new TurnOffCommand(fan);
remote.action(cmd);
}
//Receiver
static class Fan {
public void turnMin() {
System.out.println("Fan in Min degree");
}
public void turnMid() {
System.out.println("Fan in Mid degree");
}
public void turnMax() {
System.out.println("Fan in Max degree");
}
public void turnOff() {
System.out.println("Fan off");
}
}
//Command
interface ICommand {
void execute();
}
//ComcreteCommand
static class TurnMinCommand implements ICommand {
private Fan mFan;
public TurnMinCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnMin();
}
}
//ComcreteCommand
static class TurnMidCommand implements ICommand {
private Fan mFan;
public TurnMidCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnMid();
}
}
//ComcreteCommand
static class TurnMaxCommand implements ICommand {
private Fan mFan;
public TurnMaxCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnMax();
}
}
//ComcreteCommand
static class TurnOffCommand implements ICommand {
private Fan mFan;
public TurnOffCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnOff();
}
}
//Invoker
static class RemoteController {
public void action(ICommand cmd) {
cmd.execute();
}
}
}
由于遥控器已经与具体电器解耦了,以后如果想扩展新命令,只需增加即可,遥控器结构无需改动。