设计模式之命令模式

命令模式

命令模式

命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接收,怎样被操作以及是否被执行等,命令模式属于行为型模式。

在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,因为这样的实现简单明了。但紧耦合关系缺乏扩展性,在某些场合中,当需要为行为进行记录,撤销或重做等处理时,只能修改源码。而命令模式通过为请求与实现间引入一一个抽象命令接口,解耦了请求与实现,并且中间件是抽象的,它可以有不同的子类实现,因此其具备扩展性。所以,命令模式的本质是泡解耦命令请求与处理。

通用UML类图

image-20210425234623049

从UML类图中,我们可以看到,命令模式主要包含四种角色:

接收者角色( Receiver):该类负责具体实施或执行一个请求;

命令角色( Command):定义需要执行的所有命令行为;

具体命令角色( ConcreteCommand):该类内部维护一个接收者( Receiver)在其 execute()方法中调用 Receiver的相关方法;

请求者角色( Invoker):接收客户端的命令,并执行命令。

从命令模式的UML类图中,其实可以很清晰地看出: Command的出现就是作为 Receiver和 Invoker的中间件,解耦了彼此。而之所以引入 Command中间件,主要是以下两方面原因

1、解耦请求与实现:即解耦了Invoker和Receiver,因为在UML类图中,Invoker是一个具体的实现,等待接收客户端传入命令(即Invoker与客户端耦合),Invoker处于业务逻辑区域,应当是一个稳定的结构。而Receiver是属于业务功能模块,是经常变动的;如果没有Command,则Invoker紧耦合Receiver,一个稳定的结构依赖了一个不稳定的结构,就会导致整个结构都不稳定了,这也就是Command引入的原因:不仅仅是解耦请求与实现,同时稳定(Invoker)依赖稳定(Command),结构还是稳定的。

2、扩展性增强:扩展性体现在两个方面:

  • Receiver属于底层细节,可以通过更换不同的 Receiver达到不同的细节实现;
  • Command接口本身就是抽象的,本身就具备扩展性;而且由于命令对象本身就具备抽象,如果结合装饰器模式,功能扩展简直如鱼得水。

命令模式的应用场景

当系统的某项操作具备命令语义时,且命令实现不稳定(变化),那么可以通过命令模式解耦请求与实现,利用抽象命令接口使请求方代码架构稳定,封装接收方具体命令实现细节。接收方与抽象命令接口呈现弱耦合(内部方法无需一致),具备良好的扩展性。

命令模式适用于以下应用场景:

1、现实语义中具备“命令”的操作(如命令菜单,shell命令);

2、请求调用者和请求的接收者需要解耦,使得调用者和接收者不直接交互;

3、需要抽象出等待执行的行为,比如撤销(Undo)操作和恢复(Redo)等操作;

4、需要支持命令宏(即命令组合操作)。

通用代码

接收者角色( Receiver)

1
2
3
4
5
6
7
public class Receiver
{
void action()
{
System.out.println("Receiver action");
}
}

命令角色(ICommand)

1
2
3
4
public interface ICommand
{
void execute();
}

具体命令角色( ConcreteCommand)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ConcreteCommand implements ICommand
{

private Receiver receiver;

public ConcreteCommand()
{
this.receiver = new Receiver();
}

@Override
public void execute()
{
System.out.println("ConcreteCommand execute");
receiver.action();
}
}

请求者角色( Invoker)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Invoker
{
private ICommand command;

public Invoker(ICommand command)
{
this.command = command;
}

void action()
{
command.execute();
}
}

测试代码

1
2
3
4
5
6
7
8
9
public class Main
{
public static void main(String[] args)
{
ConcreteCommand command = new ConcreteCommand();
Invoker invoker = new Invoker(command);
invoker.action();
}
}

输出

1
2
ConcreteCommand execute
Receiver action

命令模式在源码中的体现

首先来看JDK中的 Runnable接口,实际上 Runnable就相当于是命令的抽象,只要是实现了 Runnable接口的类都被认为是一个线程。

1
2
3
public interface Runnable {
public abstract void run();
}

实际上调用线程的 start()方法之后,就有资格去抢CPU资源,而不需要我们自己编写获得CPU资源的逻辑。而线程抢到CPU资源后,就会执行run()方法中的内容,用 Runnable接口把用户请求和CPU执行进行了解耦。

命令模式的优缺点

优点:

1、通过引入中间件(抽象接口),解耦了命令请求与实现;

2、扩展性良好,可以很容易地增加新命令;

3、支持组合命令,支持命令队列;

4、可以在现有命令的基础上,增加额外功能(比如日志记录,结合装饰器模式更酸爽)。

缺点:

1、具体命令类可能过多;

2、命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构,解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。

打赏

请我喝杯咖啡吧~

支付宝
微信