https://blog.csdn.net/chunqiuwei/article/details/79030816
https://blog.csdn.net/panweiwei1994/article/details/75221775

说实话这个模式挺令人纠结的,但从这个模式的定义上来看,有点让人摸不到什么头脑,而且查看资料以后会发现还是有点稀里糊涂的,说懂了吧也很简单,也不懂吧也有不懂的理由,于是查阅手头的各种书籍,在此写下心得体会,算是加深一下印象。

命令模式的定义:将请求封装成一个对象,从而让用户使用不同的请求把客户端参数化,以及支持可撤销和恢复的功能。

从定义上来看着实令人一脸懵逼,在这里描述一下博主个人的理解:

请求:客户端要求系统执行的操作,在java的世界里面就是某个对象的方法。

Command:请求封装成的对象,该对象是命令模式的主角。也就是说将请求方法封装成一个命令对象,通过操作命令对象来操作请求方法。在命令模式是有若干个请求的,需要将这些请求封装成一条条命令对象,客户端只需要调用不同的命令就可以达到将请求参数化的目的。将一条条请求封装成一条条命定对象之后,客户端发起的就是一个个命令对象了,而不是原来的请求方法!

Receiver:有命令,当然有命令的接收者对象:如果有只有命令,没有接受者,那不就是光棍司令了?没有电视机或者电脑主机,你对着电视机遥控器或者电脑键盘狂按有毛用?Receiver对象的主要作用就是受到命令后执行对应的操作。对于点击遥控器发起的命令来说,电视机就是这个Receiver对象,比如按了待机键,电视机收到命令后就执行了待机操作,进入待机状态。

Client:但是有一个问题摆在眼前,命令对象现在已经有了,但是谁来负责创建命令呢?这里就引出了客户端Client对象,再命令模式中命令是有客户端来创建的。打个比方来说,操作遥控器的那个人,就是扮演的客户端的角色。人按下遥控器的不同按键,来创建一条条命令。

Invoker:现在创建命令的对象Client也已经露脸了,它负责创建一条条命令,那么谁来使用或者调度这个命令呢?--命令的使用者就是Invoker对象了,还是拿人,遥控器,电视机来做比喻,遥控器就是这个Invoker对象,遥控器负责使用客户端创建的命令对象。该Invoker对象负责要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。

上面的讲解着实有些啰嗦,下面就用看电视的人(Watcher),电视机(Television),遥控器(TeleController)来模拟一下这个命令模式,其中Watcher是Client角色,Television是Receiver角色,TeleController是Invoker角色。

首先设计一个简单的电视机的对象:

//电视机对象:提供了播放不同频道的方法
public class Television {public void playCctv1() {System.out.println("--CCTV1--");}public void playCctv2() {System.out.println("--CCTV2--");}public void playCctv3() {System.out.println("--CCTV3--");}public void playCctv4() {System.out.println("--CCTV4--");}public void playCctv5() {System.out.println("--CCTV5--");}public void playCctv6() {System.out.println("--CCTV6--");}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

电视机的类创建好了,本文会以“非命令模式“和“命令模式“两种实现看电视的不同之处,加深对命令模式的理解。

非命令模式实现
如果不用命令模式的话,其实实现看电视的功能很简单,首先设计一个看电视的人的类:Watcher;既然要看电视,所以Watcher内部需要持有一个电视机的引用。如此简单的Watcher搞定:

//观看电视的死宅类
public class Watcher {//持有一个public Television tv;public Watcher(Television tv) {this.tv = tv;}public void playCctv1() {tv.playCctv1();}public void playCctv2() {tv.playCctv2();}public void playCctv3() {tv.playCctv3();}public void playCctv4() {tv.playCctv4();}public void playCctv5() {tv.playCctv5();}public void playCctv6() {tv.playCctv6();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

所以简单的调用就实现了:

public static void main(String args[]) {Watcher watcher = new Watcher(new Television());watcher.playCctv1();watcher.playCctv2();watcher.playCctv3();watcher.playCctv4();watcher.playCctv5();watcher.playCctv6();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

执行结果:

--CCTV1--
--CCTV2--
--CCTV3--
--CCTV4--
--CCTV5--
--CCTV6--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看出Watcher类和Television完全的耦合在一起了,目前本文的电视机对象只能播放六个电视台,如果需要增添全国所有主流卫视的话,需要做如下改动:
1、修改Television对象,增加若干个的playXXTV()的方法来播放不同的卫视。
2、修改Watcher,也添加若干个对应的playXXTV()的方法,调用Television的playXXTV(),如下:

 public void playXXTV() {tv.playXXTV();}
  • 1
  • 2
  • 3

但是这明显违背了“对修改关闭,对扩展开放“的重要设计原则。
别的不说,就拿本看电视来说,比如调用playXXTV()的顺序是随即的,也就是你胡乱切换了一通:比如你沿着cctv1、cctv2、cctv3、cctv4、xxtv、yytv..nntv的顺序来看电视,也就是发生了如下调用:

        watcher.playCctv1();watcher.playCctv2();watcher.playCctv3();watcher.playCctv4();watcher.playCctv5();watcher.playCctv6();watcher.playXXtv();watcher.playYYtv();watcher.playNNtv();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

,当前你在看nntv,如果你想看上一个看过的台也就是yytv,这很简单,只要在playNNtv() 后面,调用watcher.playYYtv();即可,但是如果你想要一直回退到cctv1呢?那么你就话发生如下可怕的调用:

        watcher.playCctv1();watcher.playCctv2();watcher.playCctv3();watcher.playCctv4();watcher.playCctv5();watcher.playCctv6();watcher.playXXtv();watcher.playYYtv();watcher.playNNtv();watcher.playYYtv();watcher.playXXtv();watcher.playCctv6();watcher.playCctv5();watcher.playCctv4();watcher.playCctv3();watcher.playCctv2();watcher.playCctv1();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

为什么会这样呢?因为对于之前播放的哪个卫视并没有记录功能。是时候让命令模式来出来解决 问题了,通过命令模式的实现,对比下就能体会到命令模式的巧妙之处。

命令模式的实现

1、设计一个抽象的命令类:
在本系统中,命令的接收者对象就是电视机Tevevision了:

public abstract class Command {//命令接收者:电视机protected Television television;public Command(Television television) {this.television = television;}//命令执行abstract void execute();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

将播放各个卫视的操作封装成一个一个命令,实现如下:

//播放cctv1的命令
public class CCTV1Command extends Command {@Overridevoid execute() {television.playCctv1();}
}//播放cctv2的命令
public class CCTV6Command extends Command {@Overridevoid execute() {television.playCctv2();}
}
。。。。。。。。//播放cctv6的命令
public class CCTV1Command extends Command {@Overridevoid execute() {television.playCctv6();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

抽象类Command的几个子类实现也很简单,就是将电视机TeleVision对象的playXxTV方法分布于不同的命令对象中,且因为不同的命令对象拥有共同的抽象类,我们很容易将这些名利功能放入一个数据结构(比如数组)中来存储执行过的命令。

命令对象设计好了,那么就引入命令的调用着Invoker对象了,在此例子中电视遥控器TeleController就是扮演的这个角色:

public class TeleController {//播放记录List<Command> historyCommand = new ArrayList<Command>();//切换卫视public void switchCommand(Command command) {historyCommand.add(command);command.execute();}//遥控器返回命令public void back() {if (historyCommand.isEmpty()) {return;}int size = historyCommand.size();int preIndex = size-2<=0?0:size-2;//获取上一个播放某卫视的命令Command preCommand = historyCommand.remove(preIndex);preCommand.execute();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

很简答,遥控器对象持有一个命令集合,用来记录已经执行过的命令。新的命令对像作为switchCommand参数来添加到集合中,注意在这里就体现出了让上文所术的请求参数化的目的。且遥控器类也提供了back方法用来模拟真实遥控器的返回功能:
所以main方法的实现如下:

       //创建一个电视机Television tv = new Television();//创建一个遥控器TeleController teleController = new TeleController();teleController.switchCommand(new CCTV1Command(tv));teleController.switchCommand(new CCTV2Command(tv));teleController.switchCommand(new CCTV4Command(tv));teleController.switchCommand(new CCTV3Command(tv));teleController.switchCommand(new CCTV5Command(tv));teleController.switchCommand(new CCTV1Command(tv));teleController.switchCommand(new CCTV6Command(tv));System.out.println("------返回上一个卫视--------");//模拟遥控器返回键teleController.back();teleController.back();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

执行结果如下:

--CCTV1--
--CCTV2--
--CCTV4--
--CCTV3--
--CCTV5--
--CCTV1--
--CCTV6--
----------返回上一个卫视-------------
--CCTV1--
--CCTV5--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

从上面的例子我们可以看出,命令模式的主要特点就是将请求封装成一个个命令,以命令为参数来进行切换,达到请求参数化的目的,且还能通过集合这个数据结构来存储已经执行的请求,进行回退操作。而且如果需要添加新的电视频道,只需要添加新的命令类即可

而非命令模式中,看电视的人和电视耦合在一起;而新的命令模式则使用一个遥控器就将人和电视机解耦。总让你抱着电视机你也不乐意不是?

结合上述例子,最后用一个图来简单的表示命令模式。博主喜欢“斗图”.

好了,我对命令模式的理解到这里就结束了,如果大家发现有什么错误的地方,希望能不吝指正。至于命令模式的缺点,如果理解了该模式,他的缺点不是显而易见的吗?博主偷个懒就不再赘述。

简介

为什么要使用命令模式

在软件设计中,经常需要向某些对象发送请求,但并不知道具体是哪个对象接收请求,也不知道请求的操作是什么,只有在程序运行时才知道请求的接受者是哪个对象。这时,就可以使用命令模式来设计。命令模式可以对发送者和接受者进行解耦,发送者和接受者之间没有直接调用关系,使对象之间的调用更加灵活。

什么是命令模式

命令模式(Command Pattern):将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

别名

动作(Action)模式、事务(Transaction)模式。

类型

对象行为型模式

遵守的原则

迪米特法则、开闭原则

角色

角色

  • Command: 抽象命令类

    • 抽象类或者接口
    • 声明了用于执行请求的execute()等方法,在方法中可以调用接受者的方法。
  • ConcreteCommand: 具体命令类
    • 具体类
    • 实现了抽象命令类的方法。
    • 含有请求接受者的引用,可以调用请求接受者的方法。
  • Invoker: 调用者
    • 具体类
    • 请求的发送者
    • 通过命令类对象来执行请求。和抽象命令类之间存在关联关系,在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
  • Receiver: 接收者
    • 具体类
    • 处理请求,实现具体的业务操作。
  • Client:客户类
    • 客户。创建一个具体命令对象并制定它的接受者。

UML类图

代码实现

  • 新建接收者Receiver.java
  • 新建抽象命令类Command.java
  • 新建具体命令类ConcreteCommand.java
  • 新建调用者Invoker.java
  • 新建客户类Client.java

接收者Receiver.java

class Receiver {public void action() {// 处理请求,实现具体的业务操作System.out.println("Receiver.action()");}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

抽象命令类Command.java

abstract class Command {public abstract void execute();
}
  • 1
  • 2
  • 3

具体命令类ConcreteCommand.java

class ConcreteCommand extends Command {private Receiver receiver; // 维持一个对请求接收者对象的引用public ConcreteCommand(Receiver receiver) {super();this.receiver = receiver;}public void execute() {receiver.action(); // 调用请求接收者的业务处理方法action()}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

调用者Invoker.java

class Invoker {private Command command;// 构造注入public Invoker(Command command) {this.command = command;}// 设值注入public void setCommand(Command command) {this.command = command;}// 业务方法,用于调用命令类的execute()方法public void call() {command.execute();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

客户类Client.java

public class Client {public static void main(String[] args) {Receiver pReceiver = new Receiver();ConcreteCommand pCommand = new ConcreteCommand(pReceiver);Invoker pInvoker = new Invoker(pCommand);pInvoker.call();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

测试
运行Client.java的main()

Receiver.action()
  • 1

实例1:电视机遥控器

UML类图

代码实现

  • 新建接收者Television.java
  • 新建抽象命令类Command.java
  • 新建具体命令类OpenTVCommand.java、CloseTVCommand.java
  • 新建调用者Invoker.java
  • 新建客户类Client.java

接收者Television.java

public class Television {public void trunOn() {// 处理请求,实现具体的业务操作System.out.println("Television.trunOn(),电视打开了");}public void trunOff() {// 处理请求,实现具体的业务操作System.out.println("Television.trunOff(),电视关闭了");}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

抽象命令类Command.java

abstract class Command {public abstract void execute();
}
  • 1
  • 2
  • 3

具体命令类

OpenTVCommand.java

public class OpenTVCommand extends Command {private Television television; // 维持一个对请求接收者对象的引用public OpenTVCommand(Television television) {super();this.television = television;}public void execute() {television.trunOn();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

CloseTVCommand.java

public class CloseTVCommand extends Command {private Television television; // 维持一个对请求接收者对象的引用public CloseTVCommand(Television television) {super();this.television = television;}public void execute() {television.trunOff();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

调用者Invoker.java

class Invoker {private Command command;// 构造注入public Invoker(Command command) {this.command = command;}// 设值注入public void setCommand(Command command) {this.command = command;}// 业务方法,用于调用命令类的execute()方法public void call() {command.execute();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

客户类Client.java

public class Client {public static void main(String[] args) {Television television = new Television();OpenTVCommand openTVCommand = new OpenTVCommand(television);Invoker invoker = new Invoker(openTVCommand);invoker.call();CloseTVCommand closeTVCommand = new CloseTVCommand(television);invoker = new Invoker(closeTVCommand);invoker.call();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

测试
运行Client.java的main()

Television.trunOn(),电视打开了
Television.trunOff(),电视关闭了
  • 1
  • 2

扩展

宏命令
宏命令又称为组合命令,它是命令模式和组合模式联用的产物。

  • 宏命令也是一个具体命令,不过它包含了对其他命令对象的引用,在调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法执行一个宏命令将执行多个具体命令,从而实现对命令的批处理。
  • 一个宏命令的成员对象可以是简单命令,还可以继续是宏命令。

命令队列

当一个请求发送者发送一个请求时,可能不止一个请求接收者产生响应,这些请求接收者逐个执行业务方法,完成对请求的处理。这种情况可以通过命令对象来处理。

实现方法

增加一个CommandQueue类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。

撤销操作

实现方法

  • 在命令类中增加一个逆向操作来实现撤销操作。
  • 通过保存对象的历史状态来实现撤销(可使用备忘录模式(Memento Pattern)来实现)。

请求日志

请求日志就是将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中。

实现方法

  • 将命令对象通过序列化写到日志文件中,此时命令类必须实现Java.io.Serializable接口。

组合模式Composite可用来实现宏命令

待补充

备忘录模式可用来实现撤销功能

待补充

优缺点

优点

  • 遵守“开闭原则”。加入新的具体命令类不会影响原有系统。
  • 遵守“迪米特法则”。请求者和接受者之间不存在直接引用,实现了解耦。
  • 可以比较容易地设计一个命令队列和宏命令(组合命令)。
  • 可以方便地实现对请求的撤销(Undo)和恢复(Redo)。

缺点

  • 系统中可能会有大量的命令类。

适用环境

  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
  • 系统需要将一组操作组合在一起,即支持宏命令。
  • 支持修改日志。

使用场景

  • 待补充

问题

画出宏命令,命令队列,撤销操作,请求日志的UML结构图、并写出实现代码

在软件开发中,你在哪里用到了命令模式?

待补充。

设计模式之命令模式---转载相关推荐

  1. 设计模式 之 命令模式

    2019独角兽企业重金招聘Python工程师标准>>> 设计模式 之 命令模式 命令模式比较简单,不过多赘述 为什么需要命令模式 将"行为请求者"与"行 ...

  2. 乐在其中设计模式(C#) - 命令模式(Command Pattern)

    原文:乐在其中设计模式(C#) - 命令模式(Command Pattern) [索引页] [源码下载] 乐在其中设计模式(C#) - 命令模式(Command Pattern) 作者:webabcd ...

  3. 23种设计模式之命令模式和策略模式的区别

    文章目录 概述 命令模式 策略模式 相同点 总结 概述 命令模式和策略模式确实很相似,只是命令模式多了一个接收者(Receiver)角色.它们虽然同为行为类模式,但是两者的区别还是很明显的.策略模式的 ...

  4. 【设计模式】命令模式

    命令模式:将请求封装在对象中,客户不直接调用某个对象的方法,而是使用命令,将命令传递给拥有方法的对象从而让某一方法被调用.UML图例如以下: 以下是用C++描写的命令模式的一个简单样例: #inclu ...

  5. 一天学习一个设计模式之命令模式

    命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作. 代码如下: 1 /** 2 * 抽象命令角色类 3 */ ...

  6. 设计模式笔记-命令模式

    命令模式 最近在重新看设计模式,看了<Head First设计模式(中文版)>中的命令模式,跟上次刚入职看的感觉有点不一样,还是记录一下,便于以后查阅. 作用:将 动作的请求者 和 动作的 ...

  7. 设计模式之命令模式(行为型)

    目录 一.模式定义 二.模式角色 三.模式分析 四.典型例子 五.适用场景 一.模式定义 命令模式(Command Pattern):将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分离,两 ...

  8. 设计模式之命令模式详解(故事版)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 背景:小左是魔都某公司技术部 ...

  9. 设计模式之命令模式(C++)

    作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 一.命令模式是什么? 命令模式是一种行为型的软件设计模式,行为请求者通过发起命令,使得行为实现者 ...

最新文章

  1. Spring Boot 实现接口幂等性的 4 种方案!还有谁不会?
  2. 图像的均值和方差python_python-绘制均值和标准差
  3. [转载]js节流与防抖,防止重复提交、防止频繁重复点击
  4. Python编码风格规范
  5. C语言switch分支结构
  6. 异步通知和同步通知_CCF NOI 2020 网上同步赛报名通知
  7. oracle中sql行数的计算,Oracle技术网—如何利用DBMS_SQL包和游标计算当前用户下所有表的行数...
  8. Ivanti罗琦:IT服务管理中“拧紧螺丝”要有门道儿!
  9. SEO之Google--PageRank优化剖析(一)
  10. 另一种思路比较2个日期是否相等的方式
  11. [1.0]剖析MBR
  12. 移动磁盘故障,如何在Mac修复?
  13. Struts1的实现原理
  14. 《C和C++程序员面试秘笈》——1.4 i++与++i哪个效率更高
  15. 银行卡,身份证,驾驶证识别
  16. 计算两点间距离C++
  17. 指数退避和AIMD为什么都青睐数字2
  18. 机械臂运动学入门(二)
  19. Spring Boot系列 - 3. SpringBoot项目学习汇总
  20. 你有花生我有酒,一本学道看一天(一)

热门文章

  1. linux a卡安装教程视频下载,A卡驱动安装教程
  2. 浅析Java语言中两种异常的差别
  3. 关于技术变化方面的五种思想
  4. 《穷爸爸和富爸爸》《思考致富》读后感
  5. 可靠传输----TCP
  6. 报名通知 | 2021全国新职业技能大赛BIM技术员项目重庆市选拔赛开启
  7. MEMS加速度芯片有哪几类?
  8. 2015年第6本(英文第5本):Harry Potter 1 哈利波特与魔法石
  9. oracle统计每秒报单、撤单,报撤比例
  10. 用于呼叫控制的JAIN SIP