随风潜入夜润物无声修辞(随风潜入夜润物细无声)

“随风潜入夜,润物细无声”,用来形容这个设计模式,是最恰当不过了。这个设计模式,java程序员往往看不到它,甚至感觉不到它的存在,却在享受它带来的便利。


随风潜入夜润物无声修辞(随风潜入夜润物细无声)(1)

大家好,欢迎关注极客架构师,极客架构师——专注架构师成长,我是码农老吴。

本期是《架构师基本功之设计模式》的第10期。

在第9期,我分享了创建型设计模式中的工厂系列模式(静态工厂方法,工厂方法,抽象工厂),打造了可扩展的,支持多语言的电商平台,商品数据导出模块。

本期,我将分享行为型设计模式中,大名鼎鼎却很少现身的命令模式(command pattern),我们基于命令模式重构电商系统中的降级模块。

结论先行

根据REIS分析模型,对命令模式进行分析,命令模式包含4种角色,命令角色(command role),命令创建者角色(command creator role),命令调用者角色(command Invoker role),命令执行者角色(command executor role)。命令创建者角色将命令的参数,状态及命令执行者的相关信息,封装在独立的,可传输的,和可序列化的命令角色中,以便通过命令角色解耦合其他三个角色。最终实现命令的排队,日志及可撤销等高级功能。

基本思路

电商行业大促备战之降级策略

第一版代码(无设计模式)

第二版代码(命令模式,简版)

第三版代码(命令模式,完整版)

命令模式定义

使用REIS模型分析命令模式

互联网时代——命令模式的发展窘境

命令模式的红利

命令模式通用代码和类图

后续规划

电商行业大促备战之降级策略

自从2009年,阿里推出了第一届双十一大促活动以来,中国的电商行业进入了高速发展期。京东也不甘人后,推出了自己的大促节日——618(据说比阿里还早了一年)。自此,每年两次的电商行业大促营销活动,就成了中国广大网民的购物狂欢节,俗称剁手节。随着每年订单销售额的节节攀升,电商系统以及电商系统的架构师,程序员,也在同时经历着,血与火的洗礼。每年的两次大促,上半年的618,下半年的双十一,就成了电商行业架构师的大考,优者加官进爵,劣者卷铺盖走人。

而大促之前的备战工作,就是部门Leader和架构师大展拳脚的机会。

每当大促备战启动会召开之后,各个部门的各项工作都要有序展开,系统梳理,流量预估,完善日志,加强监控,机器扩容,系统压测,降级预案,事故演练等措施一个都不能少。

而我们今天的重点,就是降级问题。

降级的主要任务,就是弃车保帅,在系统流量超出预期时,或者服务器,数据库等资源出现异常或者短缺时,次要的业务能停就停,确保黄金流程(用户下单的流程)能正常使用。

降级的方式很多,有自动降级的,有发生异常被动降级的,也有根据订单情况,手动降级的,这些我们今天不细聊,后面在分享分布式系统架构时,我会详细的,系统的分享这些内容。今天我们只关注其中一个点,就是手动降级。

手动降级,可以通过程序员修改系统或者应用的配置参数实现降级,但是在大促期间,大家压力都比较大,修改配置参数出错的可能性比较大,加上一个大部门,有上百个应用,管理起来还是比较麻烦的。

所以实现降级的方案是,由各个应用提供降级的接口,然后由降级管理控制平台,统一进行管理。如果要对某个应用或者模块进行降级,直接点个按钮就可以了,当然通过消息系统也可以实现。

降级策略也有很多,轻一点的可以部分限流;稍微严重的还可以禁用写服务,减轻后端压力;再严重的,就需要把应用的功能下线,暂停服务。然后给用户在前台显示一个,“网络故障,稍后重试,谢谢”,把锅甩给运营商哦。

我们的案例,就以“系统下线”为例,支持系统下线的应用,通常有商品评价应用和商品推荐应用等,这两个应用,因为不属于黄金流程,而且占用资源还比较多,都属于被优先降级的重点应用。

这次我们采用spring boot框架配置项目,采用常见的三层架构的方式进行代码开发。

我们先看第一版,没有采用任何设计模式。

第一版代码(无设计模式)UML类图

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(2)

表示层:

DegradeController

服务层:

IDegradeService

DegradeServiceImpl

RPC调用:

IAppClient

RecommendAppImpl

ReviewAppImpl

其他:

AppCodeEnum

交互图

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(3)

代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.geekarchitect.patterns</groupId> <artifactId>command-pattern</artifactId> <version>0.0.1-SNAPSHOT</version> <name>command-pattern</name> <description>命令模式案例</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>

表示层:DegradeController

package com.geekarchitect.patterns.command.demo02; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; /** * @author 极客架构师@吴念 * @createTime 2022/8/29 */ @Controller public class DegradeController { private static final Logger LOG = LoggerFactory.getLogger(DegradeController.class); @Autowired private IDegradeService degradeService; public void offline(int appCode) { LOG.info("Controller层——DegradeController"); degradeService.offline(appCode); } }

服务层:

IDegradeService

package com.geekarchitect.patterns.command.demo02; /** * @author 极客架构师@吴念 * @createTime 2022/8/29 */ public interface IDegradeService { void offline(int appCode); }

DegradeServiceImpl

这里的代码是重点。

package com.geekarchitect.patterns.command.demo02; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; /** * @author 极客架构师@吴念 * @createTime 2022/8/29 */ @Service public class DegradeServiceImpl implements IDegradeService { private static final Logger LOG = LoggerFactory.getLogger(DegradeServiceImpl.class); @Autowired private ApplicationContext applicationContext; @Override public void offline(int appCode) { LOG.info("Service层——DegradeServiceImpl"); switch (AppCodeEnum.getEnumByCode(appCode)) { case REVIEW: { IAppClient appClient = (IAppClient) applicationContext.getBean("reviewApp"); appClient.offline(); break; } case RECOMMEND: { IAppClient appClient = (IAppClient) applicationContext.getBean("recommendApp"); appClient.offline(); break; } default: { LOG.info("appCode {} 不存在"); } } } }

RPC调用:

IAppClient

package com.geekarchitect.patterns.command.demo02; /** * app客户端 * * @author 极客架构师@吴念 * @createTime 2022/8/31 */ public interface IAppClient { void offline(); }

ReviewAppImpl

package com.geekarchitect.patterns.command.demo02; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * @author 极客架构师@吴念 * @createTime 2022/8/29 */ @Component("reviewApp") public class ReviewAppImpl implements IAppClient { private static final Logger LOG = LoggerFactory.getLogger(ReviewAppImpl.class); public void offline() { LOG.info("RPC层"); LOG.info("下线商品评价应用"); } }

运行结果

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(4)

方案点评

第一版代码,没有使用任何设计模式,就是凭正常的思路,按照多层架构的方式实现。有朋友看到DegradeServiceImpl里面有个switch语句,我们是不是要用命令模式,来消除switch语句呢,答案当然是否定的。命令模式的价值不是用来消除switch语句的。

那边,这版代码的问题点在哪里呢?

如果不考虑设计模式,这版代码是正常的,可以使用的。

如果从命令模式的角度看,

IAppClient接口和它的两个实现类RecommendAppImpl、ReviewAppImpl属于当之无愧的命令执行者角色,也就是一线员工,真正干活的。

而DegradeServiceImpl这个类,它充当了其他两个角色,命令创建者,命令调用者。

这里面还没有真正意义上的命令角色。

下面的第二版代码,我们只是增加了命令角色,实现了一个简版的命令模式。

我们继续。

第二版代码(命令模式,简版)UML类图

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(5)

新增接口及类:

ICommand

CommandImpl

交互图

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(6)

代码

命令角色

ICommand:命令接口

注意,命令接口里面的方法,这个方法的名称,习惯上命名为execute(),而且这个方法一般有以下潜规则。

1,没有入参:原因是参数已经通过构造函数或者set方法,传入到命令对象了。为什么不通过方法参数来传递,主要是为了方便命令调用者调用该方法。否则就需要命令调用者自己准备参数了。而命令模式中,方法参数通常是命令创建者在创建命令时,提取准备好的。

2,没有返回值:原因是命令调用者通常不关心方法的返回值,如果要返回值,还比较麻烦,需要通过回调来实现,做过多线程开发的朋友应该有这方面的经验。

总而言之,命令角色里面的方法,越简单越好。随着后面的讲解,大家就能明白其中的深意。

package com.geekarchitect.patterns.command.demo03; /** * 应用下线命令接口 * * @author 极客架构师@吴念 * @createTime 2022/8/29 */ public interface ICommand { void execute(); }

CommandImpl:命令实现类

命令实现类,它里面除了要实现接口的execute()方法外,通常包括一下要素。

1,封装方法参数的属性:如这里面的commandPara属性,这个属性通常是通过构造函数或者set方法,由命令创建者角色进行初始化。

2,代表命令执行者的属性:如这里面的appClientV2属性,这个属性也是通过构造函数或者set方法,由命令创建者角色进行初始化。在execute()方法中,调用命令执行者对象的相应方法,执行命令。

3,命令状态属性:用于存储命令执行的状态,在undo,redo等功能中使用,所以命令角色的对象,是一个有状态的对象。在web项目开发中,通常都是无状态的对象。有状态的对象一般很少见。

package com.geekarchitect.patterns.command.demo03; import com.geekarchitect.patterns.command.demo04.DegradeServiceImplV3; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; /** * @author 极客架构师@吴念 * @createTime 2022/8/29 */ public class CommandImpl implements ICommand { private static final Logger LOG = LoggerFactory.getLogger(DegradeServiceImplV3.class); private final Map<String, String> commandPara; private final IAppClientV2 appClientV2; public CommandImpl(Map<String, String> commandPara, IAppClientV2 appClientV2) { this.commandPara = commandPara; this.appClientV2 = appClientV2; } @Override public void execute() { LOG.info("入参commandPara={}", commandPara.toString()); this.appClientV2.offline(commandPara); } }

DegradeServiceImplV2

因为引入了命令角色,所以这个类里面的方法,也需要做出调整。如下:

这里面的代码,创建了命令角色的对象,并且把方法参数commandPara,以及命令执行者appClientV2

通过构造函数,传输给了命令角色对象。

那么这么多,有什么好处呢,看后面的方案点评。

package com.geekarchitect.patterns.command.demo03; import com.geekarchitect.patterns.command.demo02.AppCodeEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * @author 极客架构师@吴念 * @createTime 2022/8/29 */ @Service public class DegradeServiceImplV2 implements IDegradeServiceV2 { private static final Logger LOG = LoggerFactory.getLogger(DegradeServiceImplV2.class); @Autowired private ApplicationContext applicationContext; @Override public void offline(int appCode) { LOG.info("Service层——DegradeServiceImpl"); Map<String, String> commandPara = new HashMap<String, String>(); commandPara.put("appCode", String.valueOf(appCode)); IAppClientV2 appClientV2 = null; switch (AppCodeEnum.getEnumByCode(appCode)) { case REVIEW: { appClientV2 = (IAppClientV2) applicationContext.getBean("reviewAppImplV2"); break; } case RECOMMEND: { appClientV2 = (IAppClientV2) applicationContext.getBean("recommendAppImplV2"); break; } default: { LOG.info("appCode {} 不存在"); } } ICommand offlineCommand = new CommandImpl(commandPara, appClientV2); offlineCommand.execute(); } }

运行结果

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(7)

方案点评

第二版代码,相比第一版代码,增加了命令角色。它的好处就是,实现了DegradeServiceImplV2和命令执行者IAppClientV2的解耦合。从上面的交互图,也可以看出来,DegradeServiceImplV2类并没有调用IAppClientV2实现类里面的方法。而是通过命令角色调用的。

这版代码,从命令模式的角度看,已经增加了两个明确的角色:

命令执行者角色:IAppClient接口和它的两个实现类RecommendAppImpl、ReviewAppImpl。

命令角色:ICommand接口及CommandImpl实现类。

而DegradeServiceImplV2仍然隐形的充当着命令创建者和命令调用者角色,它既创建命令对象,又调用命令对象的execute()方法。

这版可以算是形成了命令模式的雏形,一个命令模式,命令角色作为核心,不能缺少,其他几个角色,都可以变通。而完整的命令模式,还需要把命令创建者和命令调用者分离,才能发挥命令模式的价值。

我们继续看第三版,完整的命令模式。

第三版代码(命令模式,完整版)UML类图

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(8)

新增接口及类:

ICommandManager

CommandManagerImpl

现在,命令模式的四种角色全部出现:

命令角色:ICommand、CommandImpl

命令创建者角色:IDegradeServiceV3、DegradeServiceImplV3

命令调用者角色:ICommandManager、CommandManagerImpl

命令执行者角色:IAppClient、RecommendAppImpl、ReviewAppImpl

交互图

命令模式的交互图,可以分为两个阶段:

1,创建命令阶段:由命令创建者角色,创建命令角色的对象,初始化命令对象所需的参数以及命令执行者角色,然后将命令对象,传输或者注册给命令调用者角色。

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(9)

2,执行命令阶段:由命令调用者角色,根据自身需求,自主调用命令对象里面的方法,发出执行命令的请求,由命令对象调用和它绑定的命令执行者角色的方法,执行命令。

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(10)

代码

ICommandManager

命令调用者,通常需要提供一个方法,让命令创建者,可以把创建好命令对象,传输或者注册给自己。

package com.geekarchitect.patterns.command.demo04; import com.geekarchitect.patterns.command.demo03.ICommand; /** * @author 极客架构师@吴念 * @createTime 2022/8/30 */ public interface ICommandManager { void addCommand(ICommand offlineCommand); }

CommandManagerImpl

命令调用者,它里面通常会有一个集合属性,用来存储命令创建者,传输或者注册给自己的命令对象。然后根据情况,依次从集合取出命令对象,执行命令。

这里我是采用定时器模拟的,也有可能是用户在前台触发引起的,这种GUI代码,Java程序员一般比较陌生。

package com.geekarchitect.patterns.command.demo04; import com.geekarchitect.patterns.command.demo03.ICommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; /** * @author 极客架构师@吴念 * @createTime 2022/8/30 */ @Component public class CommandManagerImpl implements ICommandManager { private static final Logger LOG = LoggerFactory.getLogger(CommandManagerImpl.class); private final List<ICommand> commandList = new ArrayList<>(); public CommandManagerImpl() { startTimeJob(); } @Override public void addCommand(ICommand offlineCommand) { LOG.info("添加命令 offlineCommand = {}", offlineCommand.toString()); commandList.add(offlineCommand); } public void startTimeJob() { TimerTask timerTask = new TimerTask() { @Override public void run() { while (commandList.size() > 0) { ICommand offlineCommand = commandList.remove(0); offlineCommand.execute(); } } }; Timer timer = new Timer(); timer.schedule(timerTask, 3000, 1000); } }

运行结果

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(11)

方案点评

这版代码,完整的实现了一个基于命令模式的降级模块。这个重构的过程,就是不断的提取出,抽象出新角色的过程。核心角色是命令角色,然后是其他三个角色。

Why,为什么要这么做?

答案很简单,就是为了解耦合,就是为了复用,就是为了支持更复杂的功能,这里我们不多说,后面会展开说明。

命令模式定义

Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.

—— Gof《Design Patterns: Elements of Reusable Object-Oriented Software》

将请求封装为一个对象,从而让你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以便支持可撤销的操作。

——Gof《设计模式:可复用面向对象软件的基础》

命令模式的概念,相比前面我们分享的其他几个设计模式的概念,要具体的多。它里面的关键词,“排队”,“日志”,“可撤销”都是非常具体的,浅显易懂的。

从这个概念,我们主要能看出,命令模式主要的目的,就是对用户的请求排队,或者记录请求日志,以便实现可撤销的功能。关于撤销功能,大家在编辑器中经常用到,撤销,恢复操作,关于这些,我们后面会结合其他几个设计模式,进行讲解。

下面我们用REIS模型,对命令模式进行分析,大家就会理解的更透彻一些。

使用REIS模型分析命令模式

REIS模型是我总结的分析设计模式的一种方法论,主要包括场景(scene),角色(role),交互(interaction),效果(effect)四个要素。

场景(Scene)

场景,也就是我们在什么情况下,遇到了什么问题,需要使用某个设计模式。

当出现以下情况时,可能需要使用命令模式。

1,当希望实现面向对象中的回调机制时:命令模式,号称面向对象中的回调函数。

2,当想让执行的操作或者命令,支持排队功能时:也就是把命令对象放入对象,依次执行。互联网项目中的消息系统,就是干这个的。

3,当想让命令支持undo,redo功能时:也就是大家在记事本,word等编辑器里面经常见到的撤销,恢复功能。这个功能,我们后面结合备忘录模式进行详细讲解。

4,当想让命令支持日志功能时:通过对执行的命令进行日志记录,可以在系统发生崩溃时,恢复系统的状态。这个功能在以前的单机版架构的软件中,有其用武之地。互联网项目,项目的状态,都记录在数据库里面,恢复系统状态,可以借用数据库的日志恢复功能,所以互联网项目中,这个功能被冷落了。

5,当希望实现事务功能时:通过命令模式实现事务功能,在早期还有用,而互联网项目中,事务主要依赖数据库,所以在实际项目中,也很少遇到。

角色(Role)

角色,一般为设计模式出现的类,或者对象。每种角色有自己的职责。

命令模式包含4种角色,命令角色(command role),命令创建者角色(command creator role),命令调用者角色(command Invoker role),命令执行者角色(command executor role),这四个角色,以命令角色为核心。

命令角色( command role):命令角色是命令模式的核心,也是其他几个角色之间的桥梁。它里面往往封装了以下几个要素。

1,执行命令所需的参数:有命令创建者角色在创建命令对象时提供。

2,命令执行者的引用:用于调用命令执行者执行命令,简化的情况下面,命令角色也可以自己执行命令,而不依赖命令执行者角色。

3,执行命令的通用接口:通常是一个execute()方法,或者是action()方法。这个方法往往比较简单,没有入参,因为入参已经封装到类里面了,所以方法就不需要入参了。也没有返回值,如果有返回值,就比较麻烦了,需要通过特殊的方法获取执行结果。

4,命令执行的状态:保持命令执行的状态,可以在执行undo等操作时使用。

命令创建者角色(command creator role):在原始设计模式中称为Client,用于创建命令角色,给命令角色设置具体的命令执行者角色,并将命令对象,传输或注册给具体的命令调用者角色。也就是命令创建者角色,只负责创建命令对象,但是不调用命令对象。

命令调用者角色(command Invoker role):在原始设计模式中称为invoker,用于调用命令对象,执行命令。如果是仅仅调用命令,好像命令调用者角色没啥用,其实,它是命令模式中重要程度仅次于命令角色的一个角色。它不仅仅是简单调用命令对象。还起到命令排队,记录命令日志,实现undo,redo等操作。它的重要性显而易见。

命令执行者角色(command executor role):在原始设计模式中称为receiver,是真正执行命令的角色,说的通俗点,就是个一线员工,干活的,而其他三个管理者,命令创建者角色,命令角色,命令调用者角色,都是管理层。三个管一个,累死这个傻小子。

交互(interaction)

交互,是指设计模式中,各种角色是如何交互的,一般用UML中的序列图,活动图来表示。简单的说就是角色之间是如何配合,完成设计模式的使命的。

命令模式,因为它通过命令角色,将命令创建者角色,命令调用者角色,命令执行者角色,进行了解耦合,所以它的交互图需要分为两个阶段,创建命令阶段和执行命令阶段。

交互图参考前面第三版代码中的交互图。

效果(effect)

效果,使用该设计模式之后,达到了什么效果,有何意义,当然,也可以说说它的缺点,或者风险。

命令模式,具有以下效果。

1,解耦合:通过命令角色,将命令创建者角色,命令调用者角色,命令执行者角色,进行了解耦合。解耦合之后,命令角色和命令调用者角色,都有很高的可重用性。一个命令角色,可以传输或者注册给多个命令调用者角色。而命令调用者角色,可以调用不同的命令角色。

2,可扩展:扩展性主要体现在命令角色。每个具体的命令子类,都封装了一个命令。当需要增加新的命令时,只需要增加命令子类即可。命令调用者的代码几乎不需要做任何修改。

3,实现命令的undo,redo功能。

4,实现宏命令:这个后面我们会结合组合模式,进行详细讲解。

总结一下,根据REIS分析模型,对命令模式进行分析,命令模式包含4种角色,命令角色(command role),命令创建者角色(command creator role),命令调用者角色(command Invoker role),命令执行者角色(command executor role)。命令创建者角色将命令的参数,状态及命令执行者的相关信息,封装在独立的,可传输的,和可序列化的命令角色中,以便通过命令角色解耦合其他三个角色。最终实现命令的排队,日志及可撤销等高级功能。

互联网时代——命令模式的发展窘境

命令模式里面的命令角色,它里面有方法执行的参数,以及方法执行的状态,所以,命令模式是有状态的,这也是它在互联网时代,发展遇到窘境的原因。互联网时代的web项目,受Http协议影响,通常是无状态的,特别是在基于多层架构的项目中,不论是控制器,服务层,还是DAO层,里面的类,基本上都没有状态。如果非要维持状态,小型Web项目系统,可以通过session来解决,而如果是分布式部署,则系统的状态,一般只能集中存储在缓存中,如redis集群中,或者干脆存储到数据库中。也就是在web项目中,对象的方法和对象的状态是分离的。这也是命令模式不太流行的原因。那么是不是说,就彻底没有用武之地呢。不是的,其实作为Java程序员,一直在享受命令模式带来的红利。

命令模式的红利

远的不说,就是上面我们刚刚使用的Timer,TimerTask,它里面就用的是命令模式,Timer是命令调用者角色,TimerTask是命令角色。类似的还有线程池,大家看看创建线程时,执行的Runnable接口,是不是,就是一个命令接口。命令模式无处不在,只不过不显山,不漏水。而且常常被并发编程中的一个设计模式——“生成者-消费者”模式,抢了风头。“生成者-消费者”模式的底层逻辑就是命令模式。

Runnable接口

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(12)

另外,在Strus框架流行的年代,程序员继承Action类,实现execute()方法,也是命令模式在为我们服务。值得一提的是,命令模式还有另外两个别名,就是Action和Transaction,Struts框架的这个接口的命名,估计就是从命令模式里面来的。关于开源软件中的命令模式,我会在相关的《源码说》里面进行详细讲解。

Struts框架的Action接口

/** * All actions <b>may</b> implement this interface, which exposes the <code>execute()</code> method. * <p> * However, as of XWork 1.1, this is <b>not</b> required and is only here to assist users. You are free to create POJOs * that honor the same contract defined by this interface without actually implementing the interface. * </p> */ public interface Action { /** * Where the logic of the action is executed. * * @return a string representing the logical result of the execution. * See constants in this interface for a list of standard result values. * @throws Exception thrown if a system level exception occurs. * <b>Note:</b> Application level exceptions should be handled by returning * an error value, such as <code>Action.ERROR</code>. */ public String execute() throws Exception; }

命令模式通用代码和类图类图

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(13)

交互图

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(14)

运行结果

随风潜入夜润物无声修辞(随风潜入夜润物细无声)(15)

相关代码,已上传到github上,大家自行下载即可。

后续规划

今天分享的命令模式,只是它的主体内容,还有两个重要功能,如下所示,我们要结合其他设计模式进行讲解。

宏命令:联合命令模式和组合模式进行讲解。

undo,redo功能:联合命令模式和备忘录模式进行讲解。

极客架构师,专注架构师成长。

关注我,我将持续分享更多架构师的相关文章和视频,我们下期见。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页