2、13命令模式
定义
命令模式将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
#解释
一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接受者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些动作,只知道如果调用execute()方法,请求的目的就能达到。
通用类图
1)Receiver接收者角色(请求方/Invoker和执行方/Receiver分开)
知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者。
2)Command命令角色(命令模式的核心)
用来声明执行操作的接口,需要执行的所有命令都在这里声明。
3)Invoke调用者角色(请求方/Invoker和执行方/Receiver分开)
要求该命令执行这个请求。接收的命令,并执行命令。
这个调用者持有一个命令对象,并在某个时间点调用命令对象的execute()方法,将请求付诸实施。
4)ConcreteCommand
将一个接收者对象绑定于一个动作,调用者只要调用execute()就可以发出请求,然后由ConcreteCommand调用接收者的一个或多个动作。
5)Client
负责创建一个ConcreteCommand,并设置其接收者。
案例1
/**
* 场景类
*/
public class Client1 {
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义接收者
Receiver receiver = new ConcreteReceiver1();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand1(receiver);
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}
/**
* 通用Receiver类
*/
abstract class Receiver{
//抽象接收者,定义每个接收者都必须完成的动作
//这里可以定义多个方法
abstract void doSomething();
}
/**
* 具体的Receiver类-1
*/
class ConcreteReceiver1 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
@Override
void doSomething() {
}
}
/**
* 具体的Receiver类-2
*/
class ConcreteReceiver2 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
@Override
void doSomething() {
}
}
/**
* 抽象的Command类
* 命令角色是命令模式的核心
*/
abstract class Command{
//每个命令类都必须有一个执行命令的方法
abstract void execute();
}
/**
* 具体的Command类-1
*/
class ConcreteCommand1 extends Command{
//对哪个Receiver类进行命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand1(Receiver receiver) {
this.receiver = receiver;
}
//必须实现一个命令
@Override
void execute() {
//业务处理,可以有多个
this.receiver.doSomething();
}
}
/**
* 具体的Command类-2
*/
class ConcreteCommand2 extends Command{
//对哪个Receiver类进行命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand2(Receiver receiver) {
this.receiver = receiver;
}
//必须实现一个命令
@Override
void execute() {
//业务处理,可以有多个
this.receiver.doSomething();
}
}
class Invoker{
private Command command;
//接收命令
public void setCommand(Command command) {
this.command = command;
}
//执行命令
public void action(){
this.command.execute();
}
}
案例2
/**
* 场景类
*/
public class Client2{
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义一个发送给接收者的命令
Command command = new RollBackCommand();
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}
/**
* 通用Receiver类
*/
abstract class Receiver{
//抽象接收者,定义每个接收者都必须完成的动作
//这里可以定义多个方法
abstract void doSomething();
}
/**
* 具体的Receiver类-1
*/
class ConcreteReceiver1 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
@Override
void doSomething() {
}
}
/**
* 具体的Receiver类-2
*/
class ConcreteReceiver2 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
@Override
void doSomething() {
}
}
/**
* 具体的Receiver类-3
*/
class ConcreteReceiver3 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
@Override
void doSomething() {
}
}
/**
* 抽象的Command类
* 命令角色是命令模式的核心
* 命令模式在实际应用中一般都会被封装掉(建议封装),减少Client对Receiver的依赖。
* 那是因为约定的优先级越高,每一个命令是对一个或多个Receiver的封装,
* 我们可以在项目中通过有意义的类名或命令名处理命令角色和接收者角色的耦合关系(这就是约定),
* 减少高层模块(Client类)对低层模块(Receiver角色类)的依赖关系,提高整体的稳定性.
*/
abstract class Command{
//定义一个子类的全局共享变量
protected final Receiver receiver;
//实现类必须定义一个接收者
public Command(Receiver receiver) {
this.receiver = receiver;
}
//每个命令类都必须有一个执行命令的方法
abstract void execute();
//回滚
public void rollBack(){
}
}
/**
* 具体的Command类-1
*/
class ConcreteCommand1 extends Command{
//声明自己的默认接收者
public ConcreteCommand1() {
super(new ConcreteReceiver1());
}
//设置新的接收者
public ConcreteCommand1(Receiver receiver) {
super(receiver);
}
//每个具体的命令都必须实现一个命令
@Override
void execute() {
//业务处理,可以有多个
super.receiver.doSomething();
}
}
/**
* 具体的Command类-2
*/
class ConcreteCommand2 extends Command{
//声明自己的默认接收者
public ConcreteCommand2() {
super(new ConcreteReceiver2());
}
//设置新的接收者
public ConcreteCommand2(Receiver receiver) {
super(receiver);
}
//每个具体的命令都必须实现一个命令
@Override
void execute() {
//业务处理,可以有多个
super.receiver.doSomething();
}
}
/**
* 撤销命令
*/
class RollBackCommand extends Command{
public RollBackCommand(){
super(new ConcreteReceiver3());
}
public RollBackCommand(Receiver receiver) {
super(receiver);
}
@Override
void execute() {
super.rollBack();
}
}
class Invoker{
private Command command;
//接收命令
public void setCommand(Command command) {
this.command = command;
}
//执行命令
public void action(){
this.command.execute();
}
}
使用场景
比如在GUI中一个按钮的点击;模拟DOS命令等触发-反馈的机制。
优点
#类间解耦、可扩展性
1)它能较容易地设计一个命令队列。
2)在需要的情况下,可以较容易地将命令记入日志。
3)允许接收请求的一方决定是否要否决请求。
4)可以容易地实现对请求的撤销和重做。
5)由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
6)最关键的优点是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分隔开。
#命令模式结合其他模式会更优秀
1)命令模式可以结合责任链模式,实现命令族解析任何;结合模板方法模式,则可以减少Command子类的膨胀问题。
缺点
如果有N个命令,那么Command的子类就有N个,那么Command会膨胀得非常大,这个就需要慎用命令模式。
其他
1)敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
2)也可以建立一个NoCommand命令对象,是一个空对象。当不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。NoCommand作为替代品,调用它的execute()方法时,这种对象什么事情都不做。
3)在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本身也被视为是一种设计模式。
4)扩展:队列请求
命令可以将运算块打包,然后将它传来传去,就像是一般的对象一样。可以利用这样的特性衍生出一些应用,例如:日程安排(scheduler)、线程池、工作队列等。
扩展:日志请求、事物处理
动作恢复到之前的状态。日志记录上次检查点之后的所有操作记录下来。
要点
1)命令模式将发出请求的对象和执行请求的对象解耦。
2)在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作。
3)调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。
4)调用者可以接受命令当做参数,甚至在运行时动态地进行。
5)命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态。
6)宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也可以支持撤销。
7)实际操作时,很常见使用”聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接收者。
8)命令也可以用来实现日志和事物系统。
标题:设计模式(十三)行为模式(对象行为型模式)命令模式-COMMAND
作者:yazong
地址:https://blog.llyweb.com/articles/2020/07/13/1594572998281.html