是不是看到“设计模式”四个字,各位读者就觉得后续内容要开始讲一些假大空的内容了?各位读者是不是有这样的感受,就是单纯讲设计模式的内容,网络上能找到很多资料,但是看过这些资料后读者很难将设计模式运用到实际的工作中。甚至出现了一种声音:设计模式是没有用的,应用系统开发就是按照需求把代码撸出来即可。出现这样的声音笔者并不意外:
-
没有目标,或者说没有“被设计的系统需要将模块耦合强度降低到什么样程度”的目标。
-
没有思路,没有如何分解功能模块的思路、没有分析模块中哪些是变化要素那些是不变要素的思路、没有如何规划模块分层的思路
-
没有目标、没有思路,就没有运用设计模式的动力,或者只是为了用而用,能达到什么样的效果并不知道。就好像拿着螺丝刀但不知道怎么下手修理机器一样,即使扭下来几颗螺丝也和要解决的问题没有什么关系。
本专题后续几篇文章,是全网少有的将设计模式带入实际设计过程中进行讲解的文章,也可能是少有的在明确了设计目标、设计思路后,使用设计模式进行设计落地讲解的文章。
1、什么是行为抽象
1.1、行为抽象的定义
行为抽象,既是基于已提取的模块业务维度、模块分层和模型,明确模块中的各个数据变化场景的过程;也是为每个数据变化场景归纳涉及的业务维度,最终分析出控制逻辑,以及控制逻辑和业务逻辑的交互设计方案的过程。
-
数据变化场景:模型和行为存在对应关系,模块中管理了哪些业务主体和关联信息,模块中就应该管理和这些业务主体、关联信息对应的行为。每个业务主体、关联信息都可能对应不止一个行为,而每个行为都可以独立地对数据进行改变。将这些行为集中起来,就成了整个功能模块应该具备的行为。
-
涉及的业务维度:每个行为改变数据时,都可能涉及到不同的业务维度。例如费用结算时,是不需要关注费用类型这个业务维度的,但需要关注不同的结算策略。例如四舍五入的结算策略、小数全部舍去的结算策略、费用小于100元也按照100元进行结算的策略等等,所以需要为每一个行为确定涉及的业务维度。
-
控制逻辑和业务逻辑:每个行为改变数据时,不止涉及的业务维度不同,工作过程也是不同的。所以有了业务维度后,就要分析出每个行为内部的控制逻辑和业务逻辑——工作过程对应控制逻辑,业务维度对应业务逻辑。
1.2、行为抽象的目标
行为抽象的工作结束后,设计人员最终应该形成以下的工作结果:
-
每个功能模块应该管控的行为
功能模块应该管控的行为是这个模块管控的各个业务主体的行为,加上这个模块管控的各关联信息的行为之和。业务主体的行为,就是根据目前的需求和未来可预测的需求变化,分析出业务主体独立进行数据变化的情况。例如:禁用、启用、创建、修改、作废、结算;关联信息的行为,就是根据目前的需求和未来可以预测的需求变化,分析出关联信息独立进行数据变化的情况。例如:建立绑定、解除绑定、查询绑定情况、修改关联关系截止有效期等。
需求一定是需要进行变化预测的,这个问题在进行业务维度分析讲解时已经详细描述过,这里就不再赘述了。总的来说就是,提取需求的业务维度,就是为需求后续可能发生的变化做的最有效准备。如果有的读者还在思考,诸如“订单支付”这样功能,到底应该属于订单模块还是应该属于支付模块这样的问题。那么就说明读者还没有阅读和理解模块分层规划的相关内容,建议先行阅读前文。
-
每个行为涉及的业务维度有哪些:
分析出每个行为涉及的业务维度,也就是分析出每个行为涉及的不变部分和变化部分。根据业务抽象的设计思想,行为的变化部分一般可形成业务维度的一个或者多个具体实现,作为控制逻辑上的一个控制点,并在具体落地时体现成具体的设计方案;行为的不变部分一般属于控制逻辑,可用来形成一条固定(或可编排)的过程控制线,将变化部分串联起来。
需要注意的是,每一个行为一定可以分离出至少一个控制逻辑和至少一个涉及的业务维度。这个描述很好理解,因为抽象的行为一定需要一种具体的落地方式,而一种具体的落地方式一定至少和一个业务维度有关,否则这个行为就不再具有抽象性。
例如,订单的“作废”功能,很简单吧。但不同订单类型对于“作废”的业务需求是不一样的,A类型、B类型或者绝大部分类型的订单,其“作废”功能就是将订单基本信息的“是否正常”这个字段置为false,就表示订单作废了。但是C类型的订单,其“作废”过程是将订单的“截止过期时间”设置为当前时间。所以订单“作废”功能涉及的业务维度就是订单类型。其控制逻辑只有一个控制点,就是根据这张订单的订单类型,找到匹配的订单类型,并调用这个订单类型具体的“作废”业务逻辑。
-
每个行为中为了完成控制逻辑对业务逻辑的控制,应采用的设计方案:
由于业务逻辑涉及具体的业务维度实现是变化的、可扩展的,所以需要为各个行为分别找到设计方式,保证控制逻辑在完全不关注具体业务逻辑只关注执行过程的前提下,能够正常工作。
例如角色模块中,设计“角色关联了哪些用户”的查询行为时,角色关联用户的形式作为一个业务维度存在于这个行为中,那么设计要求就是无论通过怎样的方式建立角色和用户的关联,查询行为的控制逻辑都能提供支持。是不是觉得不可思议?无论是角色直接关联用户,还是角色通过关联组织机构间接关联用户,还是角色通过关联用户组间接关联用户,又或者是二次开发团队要求角色通过关联生产车间间接关联用户,都属于具体的业务维度扩展,都不应该影响“角色关联了哪些用户”的查询行为中的控制逻辑——该模块中和这个行为相关的代码不应该做出哪怕一个字母的修改。
一个行为一定可以分离出它的控制逻辑和涉及的业务维度,但是如果问题场景没有达到一定的复杂度或者不能被归纳成一类问题,那么就不是每一个行为都一定要匹配一种设计模式。还有一种在进行行为抽象是可能出现的情况是,当设计人员发现多个行为都只和某一个业务维度有关时,那么最好的办法就不是为某一个行为单独匹配一个设计模式,而是为这个业务维度匹配一个设计模式,或者直接替换这个模块的整个实现。
还是以超简单的订单“作废”功能为例,设计人员在经过分析后发现订单模块中,除了“作废”功能只和订单类型有关外,“启用”、“暂停”两个功能,都只和订单类型有关。那么设计人员更好的设计选择是寻找一种设计方案,同时解决不同订单类型下,订单的“作废”、“启用”和“暂停”行为,而不是分别为订单的“作废”、“启用”、“暂停”功能分别匹配三个相同或者不同的设计模式。
2、设计模式概述
模式,是指解决一类问题通常采用的既定应对方式。如工作模式、盈利模式、经济模式、办案模式、交易模式等等;模式是理论和实践经验结合后,应对一类问题的具有一般性、简单性、重复性、结构性、稳定性、可操作性的方式。具体到设计模式,是指应对一类软件系统设计问题,经过理论和经验结合后,通常所采用的一类的设计方式。
本文会假设读者都已经比较熟练的掌握了基本的设计模式,所以本文不会探讨23中常用设计模式具体的使用场景和核心要旨, 本文只会讨论三类设计模式如何在业务抽象的模块化设计中进行落地,以及行为抽象的设计过程中,会经常使用的设计模式以及它们对应的问题场景。三大设计模式的归类描述如下图所示:
例如,本文会讨论在行为抽象后,当行为具有什么样的特征时,设计者应当被推荐采用什么样的设计模式或者设计模式的变形。但是本文不会讨论什么是命令模式、什么是适配器模式、什么是责任链模式、什么是模板方法模式。
-
创建型设计模式
这种分类下的设计模式,解决的是对象如何进行实例化创建的过程管控,诸如单例模式、多例模式、抽象工厂模式等等。它们解决的问题都只有一个,当调用者需要一个对象时,这个对象(无论是模型对象还是行为对象)应该怎么被创建出来。如果读者在工作中经常使用诸如Spring-Boot这样的开发框架,那么除非有特定场下对创建动作的管控要求,否则基本上不会直接这一类型的设计模式。
-
行为型设计模式
这一类的设计模式,是本专题内容重点讨论的设计模式。这类设计模式主要描述的多个类如何在保证较低耦合性的前提下,通过某种被设计出来的交互方式,进行协作最终实现工作目标的方式。这种协作方式,在被抽象后的功能模块中体现为控制逻辑和某一个(或几个)业务维度下的业务逻辑的协作方式。
-
结构型设计模式
这一类设计模式,也是本专题内容重点讨论的设计模式,它们主要用于解决类和类的关联方式问题,以便在满足低耦合性的前提下,形成一个更大的工作整体。这种关联可以是行为和行为之间的关联,也可以是模型和模型之间的关联。这正好可以用来解决模块中的“行为”被定义的职责任务。如果更具体来说,由于行为被抽象后,如果其控制逻辑较为复杂,那么可以使用结构型设计模式,将行为中的各个控制点串联起来。
需要说明的是,将设计模式应用到业务抽象的设计过程中,应用的形式并不是固定不变的。可能在一些简单的抽象行为中,某一种行为模式会直接进行整个工作过程的控制(单个行为模式既控制逻辑);而某些比较复杂的抽象行为中,会使用某种结构模式将多个行为模式串联起来,进行整个工作过程的控制(多个行为模式联合在一起形成控制逻辑),如采用门面模式将各个控制点联系起来。
3、行为抽象与设计模式的关系
模块中的行为可以分为控制逻辑和业务逻辑,业务逻辑和行为的可变业务维度相关,控制逻辑可以将行为中的多个可变的业务维度通过固定的或者可编排的顺序串联起来,最终形成一个工作整体完成功能任务。那么设计模式在行为抽象的工作中需要达成的目标就是两个了,用于支撑控制逻辑在控制点上对某个可变业务维度的控制进行设计落地,以及用于支撑整个控制逻辑进行设计落地。不过需要注意的是,设计模式是为了降低设计难度、降低各具体实现的扩展难度、降低模块和模块间的耦合强度,所以并不是场景下行为抽象工作最后一定需要依靠某种既定的设计模式才能落地设计。
3.1、行为抽象中常见的业务逻辑形式
本小节中我们首先讨论控制逻辑对某个可变业务维度的控制点上,可能出现的业务逻辑形式。讨论清楚后,下一个小节我们再讨论在整个控制逻辑中对多个业务维度进行业务串联时,可能出现的业务形式。在这种情况下,设计人员需要清楚的是:既然是可变化的业务维度,那么当前需求中描述的一定是这个业务维度当前的一种或者多种具体业务逻辑实现,而后续的实现需要在不改变当前任何代码的前提下支持扩展。
3.1.1、从已有的多个业务逻辑实现中,匹配一个进行执行
控制逻辑对业务逻辑的这种控制形式,是设计人员进行行为抽象分析时最常见的一种形式。既是控制逻辑要求从这个业务维度的多个实现中,找到一个匹配的实现并且执行。例如,某租车平台在车辆行驶功能中有多种计费方式,但是实际执行某一次车辆行驶功能时,只会从这些计费方式中选择出一种计费方式进行执行。如何选择出这种计费方式,可能是车辆行驶前由驾驶人员手动选定的,也可能是系统根据执行场景中的多种要素自动选定的。
并且,具体到某一个需要进行二次开发的项目上来说,计费方式这个业务维度下有哪些具体的实现,是二次开发团队可以进行扩展的——可以使用产品默认提供的多种计费方式,也可以根据实际的业务需求,在这个业务维度下扩展出新的实现。所以设计人员需要找到一种设计方式,对这种设计场景的扩展要求进行匹配。可以确定的是,策略模式正好可以解决这样的设计场景。
可以将上图中的Invoker看成是整个控制逻辑中,负责调用这个业务维度的具体控制点。除了策略模式外,理论上观察者/监听器模式都可以在这种场景下使用,但后面这两种设计模式更适合用在两个模块间的事件通信,而非用于一个模块内,从一个业务维度的多个实现中匹配一个或多个符合要求的实现。
因为讲述重心和篇幅限制的原因,本文不会讨论某种设计模式的具体落地,如果读者不清楚策略模式,大可翻阅各种网络资料(单纯讲解设计模式的内容,基本已经烂大街了,但能够将其结合复杂业务系统设计的基本没有)。如果实在不想翻阅资料也没关系,在本专题的各种实战场景中,读者都能找到策略模式如何在业务维度管控中落地的答案。
3.1.2、从已有的多个业务逻辑实现中匹配多个进行执行,执行是无顺序且无上下文关联的
这种控制逻辑在某个业务维度上的控制形式,也是设计人员进行行为抽象分析时常见的一种形式,同时也是一个业务维度下多个具体实现进行交互的最简单形式——多个具体的实现间基本没有交互。所以,设计人员只需要找到一种方式,将匹配执行要求的多个具体业务实现串联在一起,依次调用即可。而通过循环/迭代方式实现的责任链模式(变形),就可以匹配这种要求。
需要注意的是,网络上很多资料讨论责任链模式时,给出的类结构图多是一个带有自引用的责任链抽象父类(一般命名为XXApprover),但是这种结构在诸如Spring这样的开发框架下,很难进行有效且简洁的初始化。而本文讨论的是进行行为抽象后,可落地、可理解的设计方案,所以本文讨论的“责任链”模式只有两种实现来适配文中不同的业务场景,一种是基于集合进行循环/迭代实现的简单“责任链”;一种由独立的责任链控制器(一般命名为XXChain)进行递归实现的“责任链”。
这里说的是以循环/迭代方式实现的“责任链”模式,就是说在责任链上执行的主体是一个元素随机排列的集合,在编写具体代码时再通过for、while等循环语句进行集合中元素的遍历执行。这种责任链的落地方式很容易从设计、实现层面进行理解,也基本达到了降低各具体实现的耦合性的目标。但是这种实现存在一些缺陷,只是这些缺陷在目前的需求场景下,暂时还无法影响到设计要求:
-
集合中各个元素并不能相互干预,也不能相互知晓各自的执行状况:这是因为通过循环/迭代方式实现的“责任链”,其中的元素都是一个一个顺序执行,当前一个执行完成后再执行后一个。这就导致各个元素的执行都相对独立,不能各其它元素的执行过程进行干预。
-
某个元素失败后,并没有相关渠道,将失败信息传递到其它各个元素:同样由于以上的原因,就算后一个执行的元素执行失败,其失败信息(异常信息)也无法通过任何渠道通知到之前已执行完的各个元