【软件工程底层逻辑系列】建模的底层逻辑

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 在本文中,给出建模的底层逻辑:用图形逻辑地表达现实业务的抽象,通过一些大家通识的技术案例讲述建模的过程。

建模对于大家来讲并不陌生,而且建模的方法也有很多,如用例建模、四色建模、事件风暴等,但在日常工作中,大家又觉得建模挺虚的:怎么把建模落到实际开发工作中。个人认为建模是分两部分:第一部分是业务概念建模,对现实业务抽取核心概念构建出模型(知识层);第二部分是系统建模,系统建模是源于业务概念模型,遵循某些原则最终形成开发可落地的模型(操作层)。在本文中,给出建模的底层逻辑:用图形逻辑地表达现实业务的抽象,通过一些大家通识的技术案例讲述建模的过程。


一、建模的底层逻辑

建模的作用不言而喻,它可以极大简化大家对复杂事物的认识,让人能在短时间内有全局视野看清楚业务,而且建模是用面向对象的思维分析事物,也容易转化成类图。用一个公式表达建模的底层逻辑:建模 = 图形 + 逻辑 + 现实的抽象,用一句概括即是用图形逻辑地表达现实业务的抽象,接下面主要讲述图形、逻辑、现实的抽象这三部分的内容。

1.1 图形

图形即为UML图形,下面是6种对象间的关联图形,在建模中尽量遵循UML标准画图,方便大家理解。

  • 继承:子类继承父类的特性,有的语言仅支持单继承。
  • 实现:子类实现父类定义的接口。
  • 组合:一个类包含另外的类,但它强调的是一种强关联关系。
  • 聚合:也是表达一个类包含另外的类,但它的关联关系比组合要弱。
  • 关联:通过一个类可以关联到另外一个类,可分为单向关联和双向关联,一般以类的成员变量体现。
  • 依赖:比较弱的一类关联关系,一般是以接口参数的形式体现。

image.png

1.2 现实的抽象

我们的现实业务有的是非常复杂的,如果涉及到专业领域概念,新手想入门是挺难的,当我们不清楚业务链路和逻辑时,直接看代码实现时很难有全局的视野,不能够串起全局业务间的内在联系。

当我们切入到一个新领域中,并非短时间内就要成为该领域的专家,急需一种方法用20%的时间能够掌握该领域80%的业务知识,一个可行的方法就是建模,通过对现实业务进行抽象,构建出全局业务概念模型,有了全局的认识,再去了解业务细节就会快得多。模型好比地图,先做到心中有谱,再"按图索骥",我们比较迷茫时,往往是看不到全局的"地图",因此也就做不到心中有谱。

我们要建模的对象即是问题域,我们并不需要覆盖所有的点,而是基于核心的内容进行建模,Eric Evans 在建模书中提到一个观点:建模是出于某种目的而概括地反映现实,这里的概括就是抓住核心的意思。

现实的抽象有三层意思:一类是直接映射,比如现实有一个杯子,那么我们的模型中就有一个杯子;另一类是往上抽象一层映射,比如我们的组织中有一级结构、二级结构、三级结构等,那我们可以抽象成组合结构,形成父子节点的结构,这种抽象就更灵活了;最后一类是隐性抽象,前面两类抽象比较容易做到,最难的是隐性概念抽象,它很大可能是之前不存在的,需要新造出一个概念,就像软件设计中,通过新增加一层来实现解耦一样,通过新造的概念串联元素间的内在关联关系,如下面示例的中的"债权"概念。

举一个金融理财的案例,需要将投资的钱给到借款人那里,然后借款人还款时将还款给到投资人,当投资人的钱给到借款人时,就产生了债权关系,因此有一个债权的概念,这记录了投资人与借款人间的债务关系,债权就是一个桥梁。当借款还款时,对应的债权就会减少,当投资周期到了,债权就会进行债权转让,转到下一个投资人身上,当前的债权关系就结束了,下图虽然比较简单,但对理解系统至关重要。

image.png

1.3 逻辑

逻辑即为因果,也即元素之间存在某种关系,如果两个完全没有关系的元素放在一些,就会显得前因不达后果、风马牛不相及,让人不好理解。那么对应到建模上,我们的模型应该是非常具有逻辑性的,从模型上能看出业务核心要素,要素与要素之间的关系是怎样的。

逻辑主要体现在时空两个维度上:时间维度,一件事情节点完成之后,会影响后续的事情节点;空间维度,更多的体现在结构上,我们常说的空间结构就是这个意思。以时间维度为例,在电商结算中,当订单支付成功后,结算收单,收单后接受到放款的执行指令,在放款之前需要计算出放款明细信息,如卖家应收到多少钱、公司应收到多少佣金等,最后是打款转账,这个过程就是按照时间节点不断往后驱动,可抽象出如下图的模型。

image.png

空间维度即是结构关系,比如下图中的组织结构,像这样的结构类型,我们还可以找到更多的实际案例,比如交易订单有主子订单结构,执行单包含多个执行明细信息,模型最终呈现出来的就是一个结构,从结构维度也可以将其分解出更小的粒度。

image.png

不管哪种建模的方法,最根本的还是体现出了建模的逻辑性,用例建模的逻辑性体现在能够清晰地定义出用例场景,把所需要做的事讲清楚;四色建模通过人、事、物、时间、地点维度描述清楚一件事;事件风暴通过时间轴勾画出核心关键的事件。当我们构建不出模型时,并不是我们缺少建模的方法,而是建模的逻辑性上有问题,建模缺的不是方法而是经验和实践。我们不能神化建模,建模是很纯粹的,就是为了简化对复杂事物的认识,并且模型能够映射到实际开发的模型中。


二、建模的方法

建模的方法有很多种,方法多并不意味着建模的难度会很低,相反建模的门槛很高,对抽象思维要求非常高,而且建模并没有标准的答案,因此很多人觉得没啥意思。其实这里面有两部分:一部分是源于业务概念的抽象,它需要对业务的理解和抽象,往往大家在这一部分觉得没啥意思;另一部分是在业务概念模型的基础上,结合设计原则,最终设计出合理的系统模型,这部分是考验大家的设计功底。下面列举2种实践中常用的方法,这些方法并非是新的方法,是在日常工作中不断总结、实践出来的,具有更便捷实用性。

2.1 定义法

定义法建模的核心思想是通过简洁的一句话描述事物,然后根据定义进行建模,虽然方法听起来很简单,但真正用熟还需要大量的实践。

2.1.1 命令设计模式建模

命令模型在23种模式中,相对还有点复杂的模式,有一句话描述命令设计模型:A下达了命令,B接受到命令后按要求执行。将A抽象成命令发起者,B抽象成命令接受者,因此很容易找出有三个对象:命令发起者、命令接收者、命令。接下来就分析这三个对象间的关系:

  • 命令发起者与命令的关联:命令是命令发起者发起的,因此命令发起者是要感知到命令的,具体地表现就是命令发起者是要包含命令的。
  • 命令接收者与命令的关系:命令接收者是具体执行命令的对象,到底是命令接收者关系命令,还是命令关联命令接收者呢?这个可以看谁变化的可能性比较大,应该用稳定的对象包含易变的对象,根据现实经验,命令相对稳定,而命令接收者是易变的,比如做菜,今天是张三做,明天可能就换成了李四了。
  • 命令发起者与命令接收者的关系:它们之间是不会产生直接有关联的,是通过命令进行解耦的,命令发起者定义做什么,命令接收者完成具体的工作。

最终命令设计模式的概念模型如下图所示。

image.png

2.1.2 导航栏建模

店铺导航栏平时大家在浏览店铺时常遇见,可以点击不同的导航Tab看到不同的商品,比如新品、爆品等。用一句话描述导航栏:导航栏是由若干个导航Tab组成,点击导航Tab渲染出对应的页面。很容易分析出有两个对象:导航栏、导航栏Tab,但在导航栏Tab中还隐含了另外一个对象:导航栏Tab规格信息,这个规格信息描述了Tab的颜色、排序、跳转链路等信息,因此我们可以很容易画出导航栏的概念模型图。

image.png

很可能有些人觉得这个例子太简单了,如果你按照这种思路分析业务对象,是有别于传统面向过程分析的,比如导航Tab该不该展示是由它自己负责的,并不是在一个大的方法里控制的。

2.2 因果法

因果法的本质同事件风暴建模一样的,在用熟的基础上按照自己的习惯进行运用,不管什么方法,只有内化成自己的方法才有用。它的核心是基于一个对象不断往前和往后找因果关联对象,最终构建出完整的业务概念模型。

2.2.1 Spring容器建模

Spring IOC容器对大家并不陌生,以这个案例描述因果建模的方法。首先Spring IOC是一个工厂,因此有一个BeanFactory对象,同时它会包含业务Bean对象,因此最简单的一个Spring IOC 概念模型如下图所示。

image.png

接下来就需要不断追溯了,Bean是怎么到Bean工厂中的呢?应该有一个Bean注册器,在上图的基础上,再完善下。

image.png

再想这个Bean是怎么来的呢?即哪些业务对象会标识成要放到Bean工厂中的,会有一个扫描器扫描业务对象,发现有标识@Componet、@Service等注解的类需要放到Bean工厂中,因此这里面就会有三个对象:BeanDefinitionReader、BeanDefinition、BeanDefinitionRegistry,迭代后的Spring IOC概念模型如下图所示。

image.png

虽然这是一个简陋的Spring IOC概念模型图,但它还是把Spring IOC核心包含的对象展示出来了,结合现实需求,我们还可以加一些对象进来,比如考虑扩展性,会有BeanFactoryPostProcessor等。这里仅仅是一个业务概念模型,具体到系统模型需要结合设计原则来设计,如Bean工厂在实际中会拆分成多级Bean工厂继承的关系。


三、建模案例

根据第二节中讲的方法,下面通过三个技术案例描述在实际学习中的运用,通过模型可以快速让我们理解技术涉及的原理,当我们有了基础理解后,再去看技术框架源码时也会快得多。

3.1 异步事件建模

异步事件对于开发来讲并不陌生,将事件定义与事件执行进行解耦,用一句话描述异步事件:事件发布者发布一件事件后,经由事件分发器分发后,找到对应的事件监听器处理。这里面就包含了四个对象:事件发布者、事件分发器、事件监听器、事件。

事件发布者是将事件发布到事件分发器上,因此事件发布者是需要关联事件和事件分发器;事件分发器通过事件类型匹配,匹配上事件后调用对应的事件监听器进行处理,因此事件分发器包含了多个事件监听器。异步事件的概念模型图如下图所示。

image.png

在Spring事件广播器中,有添加事件监听器,以及通过事件查找到对应的事件监听器进行处理。

image.png

3.2 切面建模

切面编程在实际工作中应用得也比较多,比如在服务上增加横切功能,如日志打印、方法耗时统计等,接下来用因果建模法对切面进行建模。最开始有一个目标对象,即为我们要增加横切功能的对象,那么怎么表达这个横切功能呢?至少要包含三种信息:谁需要被增强?;增强的逻辑是什么?;什么时候增强?,描述谁需要被增强的对象抽象成切点。增强的时间节点抽象成通知。因此可得出初步的模型。

image.png

目标对象与切面是怎么关联上来的,相当于在目标对象的方法前后要额外增加一段逻辑,可以通过代理来实现,因此有一个代理对象,它会链接目标对象和切面,最终切面业务概念模型如下图所示。

image.png

当熟悉了切面业务概念模型后,再去看Spring AOP的源码会容易些,模型就是简化认识,提炼出核心关键的事物要素。

image.png

3.3 ORM框架建模

ORM是将传统SQL映射到对象上,更符合面向对象的习惯。同样我们使用因果建模的方法,ORM框架在底层最终还是要执行具体的SQL,因此将SQL语句抽象成Mapper,数据库配置信息抽象成配置信息,有了这两个配置信息,自然有对应的解析配置的类,初步的模型如下。

image.png

将执行SQL的过程抽象成会话,会话会执行SQL,将执行SQL的对象抽象成SQL执行器,在SQL执行过程中,需要做两件事:一件是参数解析;另一件是结果解析。最终的ORM框架概念模型如下图所示。

image.png

在执行器中,我们可以看到有配置信息、参数信息。

image.png

四、建模经验总结

4.1 能清晰地描述事物建模就成功了一半

建模是将混沌的事物抽象成有序关联事物的过程,混沌到有序,因此我们需要梳理清楚事物间的关联关系,如果我们都不能够清晰地描述事物,是很难建模的。不能够描述清楚事物,说明我们自己都还没有理清内在的关联关系,也就做不到抽象出有序的模型。

在日常工作中,可以尝试用几句描述一件事,看大家能不能理解到。在学校老师经常让我们概括文章的中心思想,就是锻炼这种建模能力,通过几句话把文章的核心内容勾画出来。我们在软件建模也是一样的,通过几句话把业务的涉众、业务结构、业务目标、核心关注点表达出来,然后通过业务概念模型呈现出来。

4.2 通过描述事物的结构是建模常用的方法

模型最终呈现出来的是一个图,这个图往往是有清晰的结构,比如房屋模型,它就有自己的结构:坐北朝南,有大厅、睡房、厨房、洗手间等,软件模型也是一样的,在2.1.2节中举了一个店铺导航栏的例子,如下图所示,当我们看到店铺导航栏时,可以想像下它是怎样的结构。

image.png

在实际的业务中,有两类模型:一类是偏结构型的模型;另一类偏行为型的模型,从个人接触到的业务看,结构型的模型偏多,比如订单有主子订单模型。我们也可以尝试从结构的角度去定义事物。

4.3 建模要区分知识层和操作层

模型一般有两层:一层是知识层;另一层是操作层,知识层中包含的概念是通识的概念,往往是面向使用者,比如我们在使用AOP切面编程时,可以用@Aspect、@PonitCut、@Before等注解时,它们就是属于知识层,知识层的内容抽象层次比较高,大家比较容易理解,我们所做的业务运营工作台也是做的知识层的内容,比如配置页面模板、站点配置等,这些可枚举的维度都是知识层。

另一层是操作层,操作层是支撑知识层的,还是AOP为例,底层有对注解的解析、动态代理的生成等;再比如Spring IOC中,有各类Bean的注解,在操作层中,有BeanDefinition的加载、识别、解析、Bean实例化、Bean初始化等,操作层中的实现也并非面向过程的设计思维,它也包含了抽象设计和遵循某些设计原则,这一点在下一节中会专门讲到,同样的设计,张三和李四可能不一样,正所谓"文无第一,武无第二",大家的设计理念、角度不一样,技术设计本应如此,就像一百个厨师做同样的菜,做出来的菜也有一百个样。

如果知识层偏表达,那么操作层就是偏实现支撑,这两类的视角也是不一样的,知识层更抽象,而往往我们开发人员更关心的是操作层中的具体实现类,缺乏抽象意识,这也是我们自己心中非常清楚,但别人很难理解,需要转换视角,思考通识的知识层有什么。


五、小结

在文章中主要讲述了建模的一些方法,方法虽然简单,但用熟还是需要大量的实践经验,建模分为两个阶段:第一个阶段是业务概念建模,它无关于技术,是通识的建模(知识层);第二个阶段是系统建模,基于业务概念建模的基础上,考虑具体的技术实现(操作层),遵循某些设计原则构建可落地的模型。建模的确会简化对复杂事物的认识,以学习技术框架为例,如果一头扎进源码中,很难有全局观,相反如果我们从全局对技术框架有一定的理解,再去看源码也会快很多。


作者 | 高福来(不拔)

来源 | 阿里云开发者公众号

相关文章
|
7月前
|
架构师 测试技术 Linux
嵌入式软件架构中抽象层设计方法
嵌入式软件架构中抽象层设计方法
244 0
|
4月前
|
uml
建模底层逻辑问题之在建模时,对现实进行抽象该如何操作
建模底层逻辑问题之在建模时,对现实进行抽象该如何操作
|
4月前
|
SQL Java 数据库
建模底层逻辑问题之ORM框架建模中,执行SQL的过程中被抽象和组织是如何实现的
建模底层逻辑问题之ORM框架建模中,执行SQL的过程中被抽象和组织是如何实现的
|
4月前
|
设计模式
建模底层逻辑问题之以命令设计模式为例,要用定义法建模,如何实现
建模底层逻辑问题之以命令设计模式为例,要用定义法建模,如何实现
|
2月前
|
编译器 Serverless C++
【C++】C++STL 揭秘:Strng背后的底层逻辑(一)
【C++】C++STL 揭秘:Strng背后的底层逻辑
|
2月前
|
存储 C++ 索引
【C++】C++STL 揭秘:Strng背后的底层逻辑(二)
【C++】C++STL 揭秘:Strng背后的底层逻辑
|
2月前
|
C++
【C++】C++STL 揭秘:Strng背后的底层逻辑(三)
【C++】C++STL 揭秘:Strng背后的底层逻辑
|
6月前
|
SQL 存储 缓存
第四章 逻辑架构(1)
第四章 逻辑架构
42 1
|
6月前
|
SQL 存储 缓存
第四章 逻辑架构(2)
第四章 逻辑架构
38 1
|
7月前
针对抽象编程与对应的好处
针对抽象编程与对应的好处
54 1