冬季学期12月30日下午六点,开源社区在东区计算机楼504举行了本学期的第三次活动。

本次活动邀请到了@丁一凡给大家分享自己的心得体会。

学长因为有点发烧带了口罩

感谢带病来参加活动的学长~手动笔心心~

学长的讲解也很细致,内容大致分为下面几个部分:

1.介绍了模型,领域的概念

2.介绍了项目开发的过程

3.从知识消化到深层模型的一个过程

4.介绍了Strategy模式

中间也举了很多例子,很通俗易懂。

以及学长想送给大家的话:

知识消化是一种探索,它永无止境。

学长的不完整版文档:

今天我分享的主要内容不是特别geekstyle的东西,就是不会讲一些特别深入细化的方面的知识,而是给大家讲一些比较宏观的,不论是开发人员还是设计者、管理者都能听懂的,老少咸宜的知识;并希望大家从这些比较偏概念的知识里对自己在实际开发过程中的一些方面起到有益的指引作用。

今天我要分享的是一个2007年左右慢慢兴起的概念————领域驱动设计。本次分享主要目的是希望大家了解如何从项目相关的问题区域提取出适合映射在软件工程中的模型,简单的说就是知识消化,以便大家对于一种叫做Model-Driven Design的设计模式有一个初步的了解。

首先先理解一下模型这个概念。模型是对现实的解释,通常用来描绘人们所关注的现实或想法的某个方面,其特征通常是把与解决问题密切相关的方面抽象出来,而忽略无关的细节。我们来看一下这张图,这是18世纪中国描绘的世界地图,图中央最大的部分是中国,其周围散布着其他国家,但这些国家只是草草地表示了一下。这是适用于当时中国社会的世界模型,它意在关注中国自身。

然后在理解一下领域这个概念。就从我们开发人员的角度来说,开发一个软件就是为了执行用户的某项活动,或是满足用户的某种需求。比如开发一个外卖app就是为了执行顾客点外卖的操作和商户通过互联网接受外卖订单的操作,满足了顾客和商户各自在他们所共同关注的问题区域————餐饮业中的外卖服务上的消息交换和业务处理的需求。其中该app所相关的‘领域’就是外卖这一问题区域。

那么比方说我们要做一个项目开发,是不是就会一步步地联想到:这个项目中有啥需求?实现这些需求应该选用哪些技术?……如果接到一个项目条件反射地以这种思路去分析的话就是典型的程序员poi的思维了。实际上针对不同的领域,学过面向对象程序设计这门上大计算机学院必修课的同学可能会自然的联想到要给领域中的一些概念建立类,以面向对象的设计思路去做开发工作。这其实就是已经有点提取模型的意思在里面了。

那么系统的来讲,我们且不说是站在开发人员的角度,我们站在一个更加宏观的,架构师的角度来分析一个项目,会在项目开发的前期准备中进行以下活动。

建模是一个必不可少的环节,那我们要怎么有效的建模呢?怎么建模才是合适的,信达雅的呢?其中有这几点要素:

1) 模型和实现的绑定,这里强调的是抽象概念和实体的联系

2) 建立一种基于模型的语言,这个‘语言’指的是用于协作沟通的术语,旨在开发人员和领域专家无需特地解释与翻译即可理解互相要表达的意思。

3) 开发一个蕴含丰富知识的模型。所谓模型不单单是数据模式,学过面向对象程序设计的同学会知道,一个类除了有数据成员,很多时候不可或缺的是成员方法。当然这只是其中一个层面的模型体现,更多的时候我们关注的是高层构造块和基础构造块之间的模型关系。

4) 提炼模型。在优化模型的过程中,重要的概念被不断的添加到模型中,不再使用的或者不重要的概念会从模型中被移除。

5) 头脑风暴和实验。这就是讨论和试水嘛。在讨论时,一定要试着一步步检查或分步讨论一个个场景,这时候口头表述的清楚与否本身就可做为所提议的模型的一种可行性测试。毕竟说都说不清楚谈何建模?

说到这里,大家可以思考一下,如果我们要做个像英雄联盟那样的游戏,要怎么建模。这游戏里有英雄,小兵,野怪,他们的关系是什么?共同点在哪里?区别在哪里?他们都会触发的相似的事件有哪些?……通过对这些问题的思考,大家对这三个概念实体的数据成员和所需要注入的服务是不是有所领悟了?然后你项目里的interface,class,service,是不是感觉渐渐浮出水面了?

之前讲的这些东西都是让大家对模型和领域有着一些初步的概念和认知。下面讲一些细化的东西,首先是从知识消化到深层模型的一个过程。

知识消化是一个很形象的说法,这个环节就是让领域专家在大量的信息中探寻有用的部分,不断尝试各种信息组织方式,并寻找对大量信息有意义的简单视图。(比如英雄联盟的那个建模例子)其中……

领域专家把这些知识消化的工作完成以后,是不是就只管设计个概念模型,然后把特征和需求讲给开发人员听,让他们自己去建实际开发过程中需要的模型就行了?

有些团队可能是这么做的,但是其中可能会导致很多问题和疏漏。

下面先用一个例子给大家感受一下知识消化的过程。这里有个程序的任务是被用来预订一艘船在一次航程重要运载的货物。我们规定这个应用程序的任务是将每件货物与一次航程关联起来,记录并跟踪这种关系。光考虑这个还是感觉挺简单的,就是个表示货运订单的方法嘛。这里给出个示例代码给大家感受一下。大家不用管它是啥语言的,只要知道这个也是能够表示面向对象设计方法的语言就行了。

这个方法展示的就是预订一次货运的流程。是不是感觉挺简单的,其中我们关心到的概念实体也就航运和货物,这里还有个表示确认事件的类,是聚合在航程类中的一个起多状态标识作用的接口类。我们在整个订单系统中请求订单确认序列服务的确认下一订单事项方法来创建当前我们要用到的确认事件,在进行确认一次货运航程的过程中,可能在这个服务体内部还有一些类似于统计总订单量的方法同时被触发了,考虑到易扩展性等因素,这个服务体得以存在。

不过这里我要讲的重点是开发人员不一定知道一件事情:由于总会有人在最后一刻取消订单,因此行于也的一般做法是接受比其运载能力多一些的货物。这称为“超订”,有了解过美联航强行拖人下飞机的那个事件的同学可能会对这个概念有点印象。不过这个的确是航运领域的一个基本策略,这个策略的表现形式有时是使用一个简单的容量百分比来表示,如预订110%的载货量,有时则采用复杂的规则,比如给那种主要客户或者特定种类的货物优先运载之类的。

于是知道了“超订”规则,我们对航运领域有了一定的了解,知道了航运中有表示运载能力的数据,有记录当前运载量的数据,有表示当前航运订单状态的数据,与此同时又知道了货运这个概念里也有运载容量这个数据用以区分需要不同运载能力大小的不同货物。这样下来我们的类图就变成这样子了,大家体会一下。

然后刚才那个记录一次订单的方法就变成这样了,看这边我特地上下加了空行的部分,这个部分被称为“卫语句”,就是指起保护作用的语句的意思。大家体会一下这个方法,是不是感觉到这个重要的超订规则就被包含在这个卫语句中了,一种抽象实体和模型紧密联系的感觉是不是油然而生?!但是刚才又说道航运领域中有的时候会采用其他策略,比如刚才说的先让主要客户或者特定种类的货物优先运载之类的。那么就是说要改策咯咯,我们就要把这个卫语句再改成别的条件,是不是要写好几种不同的记录订单的方法啊?聪明的同学一定想到了,把这个卫语句抽离出来变成一个函数,别的策略也抽象成不同的函数,方便替换。这种设计思路被称为STRATEGY模式。

Strategy模式一般是指……

这里我们把代码修改一下,变成这样……

这里调用的overbookingPolicy服务里包含了这个方法用判断是否通过110%超订规则,想把记录订单函数设计的更灵活的可以再加个形参用来替换不同的规则。

在这不断了解领域然后对应模型修改来修改代码的过程中,知识消化就在逐渐进行了。不过并不建议将这样今昔的设计应用到领域的每个细节中。我们在之后的学习中还要学会如何关注重点以及如何隔离其他问题或使这些问题问题最小化。这个例子的目的是为了说明领域模型和相应的设计是可以被用来保护和共享知识的。在设计的过程中,开发人员和领域专家都了解了超订规则的本质,明白它是一个明确且重要的业务规则,而不是一个不起眼的条件判断。同时我也建议开发人员给领域专家展示部分开发过程中的技术方法,甚至是代码也行,但是一定要在程序员的指导下给领域专家讲解,以便形成反馈闭环,领域专家发现问题可以及时反馈给开发人员,开发人员在开发过程中发现有不合理或者不清楚的部分可以让领域专家做出修正或者解释。

有用的模型很少停留在表面,随着对领域和应用程序需求的理解逐步加深,我们往往会丢弃一开始看起来很重要但是却只是浮于表面的元素,或者切换看问题的角度。实际上刚才讲的航运领域中货物的处理,如装货卸货运货确认挂起等,全部都是一系列的责任的传递。这个责任机制可能关系到不同的运输商,收货人等。那么我们就会基于发现的深层的概念实体建立更合适的领域模型,同时修改我们的代码,越走越深。

知识消化是一种探索,它永无止境。

附录
本次活动的PPT