命令设计模式很多人看了官方的文档是不够清晰的,甚至看了一遍基本记不住,说简单的谈不上,说难的话就那么一点代码,所以思想很重要,经过自己摸索后的一些理解,本文用最形象深刻的例子来带大家深刻理解命令设计模式的核心思路和最佳实践。

1、定义

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

咋一看不明白写的什么,什么请求封装对象,还有参数化,还有撤销和恢复?

其实请求的意思是:客户端要求系统执行的操作,在Java里指的是某个对象的某个方法,如Animal的eat()方法,这个就是一个吃的请求。

附一张图是结构介绍,看不明白的话看后面介绍的图对比着看:

2、组成部分

命令模式有三部分组成

  • Command:请求封装成的对象,该对象是命令模式的主角。也就是说将请求方法封装成一个命令对象,通过操作命令对象来操作请求方法。在命令模式是有N个请求的,需要将这些请求封装成一条条命令对象,客户端只需要调用不同的命令就可以达到将请求参数化的目的。将一条条请求封装成一条条命令对象之后,客户端发起的就是一个个命令对象了,而不是原来的请求方法!
  • Receiver:有命令,自然会有命令的接收者对象了。如果有只有命令,没有接受者,那不就是光棍司令了?举个例子,如果没有电视机,你对着遥控器或者狂按有毛用?Receiver对象的主要作用就是受到命令后执行对应的操作。对于点击遥控器发起的命令来说,电视机就是这个Receiver对象,比如按了待机键,电视机收到命令后就执行了待机操作,进入待机状态。
  • Invoker:一条条命令已经组装成了一个一个Command,那么谁来使用或者调度这个命令呢?命令的使用者就是Invoker对象了。拿人,遥控器,电视机来做比喻,遥控器就是这个Invoker对象,遥控器负责使用客户端创建的命令对象。该Invoker对象负责要求命令对象执行请求,通常会持有命令对象,并且可以持有很多的命令对象。

对于形象一点的图,我设计了一个看电视的场景:

3、案例讲解

如果我们不使用命令设计模式,来设计一个场景:

我是一个屌丝,在家里看电视,我要看很多台比如CCTV1——CCTV6,而且我还可以返回上一个或者上N个台,我应该怎么实现?

其中有四个个角色,我(Client),电视机(Receiver),遥控器(Invoker),还有我要下命令去按台(ConcreteCommand)

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

/*** @Description:* @title: Television* @Author Star_Chen* @Date: 2021/12/20 23:21* @Version 1.0*/
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--");}
}

电视机有了,写个屌丝Watcher,因为屌丝要看电视,所以要持有一个电视机的引用

/*** @Description:* @title: Watcher* @Author Star_Chen* @Date: 2021/12/20 23:22* @Version 1.0*/
public class Watcher {/*** @Date: 2021/12/20 23:23* @Description: 持有Television*/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();}
}

这个时候我们就可以看电视了,发个命令ClientMain

/*** @Description:* @title: ClientMain* @Author Star_Chen* @Date: 2021/12/20 23:23* @Version 1.0*/
public class ClientMain {public static void main(String[] args) {Watcher watcher = new Watcher(new Television());watcher.playCCTV1();watcher.playCCTV2();watcher.playCCTV3();watcher.playCCTV4();}
}

执行结果很简单

--CCTV1--
--CCTV2--
--CCTV3--
--CCTV4--

发现没有,如果我这个时候要添加一个CCTV5体育频道,要改动两个类,一个是Television,一个是屌丝Watcher,增加N个playCCTVN()方法;此时人和电视因为这些一个一个的指令被耦合一起了,等于屌丝要随时抱着电视。明显违背了“对修改关闭,对扩展开放“的重要设计原则。

并且如果我想回退到刚刚的CCTV1,我是不是得这么写:

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

命令模式出场,分析下命令模式的几个关键Object,很明显这里的Television就是我们的Receiver,来接受命令的;那么谁来执行命令呢?很明显是遥控器RemoteControl;这个时候屌丝就不存在了,因为没有他的角色,你可以理解为他是Client;

对于Command我们抽象出来一个一个接口

/*** @Description: 每个请求封装成的命令对象,客户端只需要调用不同的命令就可以达到将请求到不用的方法上* @title: Command* @Author Star_Chen* @Date: 2021/12/20 23:36* @Version 1.0*/
public abstract class Command {/*** protected修饰的对象或者方法,子类* 1、父类的protected成员是在同一个包内可见的,并且对子类可见* 2、若子类与父类类不在同一包中,假如在两个不同的包里,那么在子类中,子类实例可以访问其从父类继承而来的protected方法,而不能访问父类实例(new出来的)的protected方法*/protected Television television;/*** @Date: 2021/12/20 23:36* @Description: 抽象类的构造方法,是给子类使用的;*/public Command(Television television) {this.television = television;}/*** @Date: 2021/12/20 23:37* @Description: 抽象的命令执行方法*/abstract void execute();
}

有多少个CCTV台的指令,就实例化出多少个ConcreteCommand,这里我只写一个就可以了。

/*** @Description: CCTV1的执行命令* @title: CCTV1Command* @Author Star_Chen* @Date: 2021/12/20 23:19* @Version 1.0*/
public class CCTV1Command extends Command {/*** @Date: 2021/12/20 23:20* @Description: 构造传入*/public CCTV1Command(Television television) {super(television);}/*** @Date: 2021/12/20 23:20* @Description:*/@Overridevoid execute() {television.playCctv1();}
}

接下来谁来接受命令呢,当然是Television作为Receiver

/*** @Description: 电视机类充当Receiver,命令的接受者对象* @title: Television* @Author Star_Chen* @Date: 2021/12/20 23:35* @Version 1.0*/
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--");}
}

最后一个就是Invoker,谁来调用命令,当然是RemoteControl,因为不同的命令对象ConcreteCommand拥有共同的抽象类,我们很容易将这些名利功能放入一个数据结构(比如数组)中来存储执行过的命令;遥控器对象持有一个命令集合,用来记录已经执行过的命令。新的命令对象作为switchTV参数来添加到集合中,注意在这里就体现出了让上文所说的请求参数化的目的,来达成撤回或者恢复效果

/*** @Description: 遥控器,充当invoker* @title: RemoteControl* @Author Star_Chen* @Date: 2021/12/20 23:35* @Version 1.0*/
public class RemoteControl {List<Command> historyCommand = new ArrayList<Command>();/*** @Date: 2021/12/20 23:40* @Description: 切换卫视*/public void switchTV(Command command) {historyCommand.add(command);command.execute();}/*** @Date: 2021/12/20 23:39* @Description: 遥控器返回命令*/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();}}

那么剩下就是Client,也就是Main

/*** @Description: 充当Client* @title: CommandMain* @Author Star_Chen* @Date: 2021/12/20 23:43* @Version 1.0*/
public class CommandMain {public static void main(String[] args) {Television tv = new Television();RemoteControl remoteControl = new RemoteControl();remoteControl.switchTV(new CCTV1Command(tv));remoteControl.switchTV(new CCTV2Command(tv));remoteControl.switchTV(new CCTV3Command(tv));    //切换上一个台remoteControl.back();}
}

4、总结

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

那么有没有缺点呢?当然有,如果这些请求越多,意味着要执行的命令越多,项目里会大量的存在Command的实现类,虽然满足了单一职责,但是也太单一了,维护是个很麻烦的事情;

优点也看得出来,在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者(屌丝)"与"行为实现者(电视)"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合,这是命令模式的使用场景,说白了就是借助个中间层面的东西——命令,来实现解耦。

        好了,我对命令模式的理解到这里就结束了,如果大家发现有什么错误的地方,希望能不吝指正!!

设计模式篇之——命令设计模式相关推荐

  1. 设计模式示例_命令设计模式示例

    设计模式示例 本文是我们名为" Java设计模式 "的学院课程的一部分. 在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们. 您将了解模式如此重要的原因 ...

  2. Java设计模式(六):命令设计模式

    1.应用场景 有时候需要向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道请求的操作是什么,将'对象的请求者'从'命令的执行者'中解耦.使用此模式的优点还在于,command对象拥有更长的生命 ...

  3. 设计模式篇之——策略设计模式

    策略模式在实际开发过程中其实非常的常见,那么本章也从原始需求扩展到设计模式上是如何更好的落地的. 1.定义 分别封装行为接口,实现算法族,父类里放行为接口对象,在子类里具体设定行为对象.一个类的行为或 ...

  4. 行为设计模式 - 命令设计模式

    行为设计模式 - 命令设计模式 命令模式是行为设计模式之一.命令设计模式用于在请求 - 响应模型中实现松散耦合. 目录[ 隐藏 ] 1命令模式 1.1命令设计模式示例 1.2命令模式接收器类 1.3命 ...

  5. 2021-12-11 WPF面试题 WPF中的命令设计模式是什么

    WPF中的命令设计模式是什么 命令设计模式是面向对象设计模式中最强大的设计模式之一. 此模式允许将操作请求与实际执行操作的对象分离,换句话说,命令模式将操作表示为对象. Command 对象不包含要执 ...

  6. 程序员内功-设计模式篇

    一. 什么是设计模式 纠结了好久,今天终于下定决心开始写设计模式系列,因为这个系列章节确实不好写,在这之前,也看了好多关于设计模式的博客.视频.书籍等,最后结合自己的理解,亲自动手实操代码,完成该章节 ...

  7. [Head First设计模式]餐馆中的设计模式——命令模式

    系列文章 [Head First设计模式]山西面馆中的设计模式--装饰者模式 [Head First设计模式]山西面馆中的设计模式--观察者模式 [Head First设计模式]山西面馆中的设计模式- ...

  8. 【白话设计模式八】命令模式(Command)

    为什么80%的码农都做不了架构师?>>>    #0 系列目录# 白话设计模式 工厂模式 单例模式 [白话设计模式一]简单工厂模式(Simple Factory) [白话设计模式二] ...

  9. Java多线程编程实战指南+设计模式篇pdf

    下载地址:网盘下载 随着CPU 多核时代的到来,多线程编程在充分利用计算资源.提高软件服务质量方面扮演了越来越重要的角色.而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案.然 ...

最新文章

  1. 如何使用 Python 构建推荐引擎?
  2. LINUX中printf与echo的区别
  3. 蓝色巨人将磁带定位为数据存储的集成归档层
  4. Kafka如何更新元数据到各broker节点
  5. 10种开发以及改善应用的低成本方法
  6. Win10 OneDrive无法同步文件怎么办?一个命令搞定
  7. x264 编码器选项分析 (x264 Codec Strong and Weak Points) 1
  8. 今天博客园肿了吗?希望团队修复一下
  9. 班级管理系统java_基于Java班级管理系统
  10. 七.项目管理基础知识
  11. jquery 常见特效_常见jQuery错误的解决方案
  12. 微信分享只有链接没有图标和标题正文
  13. 华为机试 - HJ10 字符个数统计
  14. Golang go mod 使用
  15. fpga控制vga显示彩色图片
  16. 调研报告之——可见光通信与可见光定位
  17. linux wifi开机自动连接 wifi连接
  18. 进入和使用WinRE恢复环境
  19. 人工智能是什么?人工智能行业前景如何
  20. application reload

热门文章

  1. 中国安全态势越来越好,专访山石网科CSO蒋东毅 | 拟合
  2. ECCV 2020 | 对损失信息进行建模,实现信号处理高保真还原
  3. 32岁程序员,补偿N+2:“谢谢裁我,让我翻倍!” 网友:榜样!
  4. 周志华等人新著!国内第一部AI本科专业教育培养体系出炉
  5. 为何Google将几十亿行源代码放在一个仓库?
  6. AI一分钟 | 微软将成立微软亚洲研究院上海分院;阿里AI鉴黄师能判别呻吟声
  7. 阿里二面:GET 请求能传图片吗?
  8. 去大厂面试,说了没高并发经验,面试官还是抓着这个问!
  9. 面试官:Java中 serialVersionUID 的作用是什么?举个例子说明
  10. 为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?