一、模式:MODEL-DRIVEN DESIGN
模型种类繁多,作用各有不同,即使是那些仅用于软件开发项目的模型也是如此。领域驱动设计要求模型不仅能够指导早期的分析工作,还应该成为设计的基础。这种设计方法对于代码的编写有着重要的意义。不太明显的一点就是:领域驱动设计要求一种不同的建模方法。。。
严格按照基础模型来编写代码,能够使代码更好地表达设计含义,并且使模型与实际的系统相契合。
那些压根儿就没有领域模型的项目,仅仅通过编写代码来实现一个又一个的功能,它们无法利用前两章所讨论的知识消化和沟通所带来的好处。如果涉及复杂的领域就会使项目举步维艰。
另一方面,许多复杂项目确实在尝试使用某种形式的领域模型,但是并没有把代码的编写与模型紧密联系起来。这些项目所设计的模型,在项目初期还可能用来做一些探索工作,但是随着项目的进展,这些模型与项目渐行渐远,甚至还会起误导作用。所有在模型上花费的精力都无法保证程序设计的正确性,因为模型和设计是不同的。
模型和程序设计之间的联系可能在很多情况下被破坏,很多设计方法都提倡使用完全脱离于程序设计的分析模型,并且通常这二者是由不同的人员开发的。之所以称其为分析模型,是因为它是对业务领域进行分析的结果,它在组织业务领域中的概念时,完全不去考虑自己在软件系统中将会起到的作用。在创建分析模型时并没有考虑程序设计的问题,因此分析模型很有可能无法满足程序设计的需求。
纯粹的分析模型甚至在实现理解领域这一主要目的方面也捉襟见肘,因为在程序设计和实现过程中总是会发现一些关键的知识点,而细节问题则会出人意料地层出不穷。
前期模型可能会深入研究一些不相关的问题,反而忽略了一些重要的方面。而且它对于其他问题的描述也可能对应用程序没有任何帮助。最后的结果就是:编码工作一开始,纯粹的分析模型就被抛到一边,大部分的模型都需要重新设计。即便是重新设计,如果开发人员认为分析与程序开发毫不相关,那么建模过程就不会那么规范。
如果整个程序设计或者其核心部分没有与领域模型相对应,那么这个模型就是没有价值的,软件的正确性也值得怀疑。同时,模型和设计功能之间过于复杂的对应关系也是难于理解的,在实际项目中,当设计改变时也无法维护这种关系。若分析与和设计之间产生严重分歧,那么在分析和设计活动中所获得的知识就无法彼此共享。
分析工作一定要抓住领域内的基础概念,并且用易于理解和易于表达的方式描述出来。设计工作则需要指定一套可以由项目中使用的编程工具创建的组件,使项目可以在目标部署环境中高效运行,并且能够正确解决应用程序所遇到的问题。
MODEL-DRIVEN DESIGN(模型驱动设计)不再将分析模型和程序设计分离开,而是寻求一种能够满足这两方面需求的单一模型。不考虑纯粹的技术问题,程序设计中的每个对象都反映了模型中所描述的相应概念。这就要求我们以更高的标准来选择型,因为它必须同时满足两种完全不同的目标。
绑定模型和程序设计是切实可行的。但是这种绑定不能够因为技术考虑而削弱分析的功能,我们也不能接受那些只反映了领域概念却舍弃了软件设计原则的拙劣设计。模型和设计的绑定需要的是在分析和程序设计阶段都能发挥良好作用的模型。如果模型对于程序的实现来说显得不太实用时,我们必须重新设计它。而如果模型无法忠实地描述领域的关键概念,也必须重新设计它。这样,建模和程序设计就结合为一个统一的迭代开发过程。
将领域模型和程序设计紧密联系在一起绝对是必要的,这也使得在众多可选模型中选择最适用的模型时,又多了一条选择标准。它要求我们认真思考,并且通常会经过多次反复修改和重新构建的过程,但是通过这样的过程可以得到与设计关联的模型。
结论:
软件系统各个部分的设计应该忠实地反映领域模型,以便体现出这二者之间的明确对应关系。我们应该反复检查并修改模型,以便软件可以更加自然地实现模型,即使想让模型反映出更深层次的领域概念时也应如此。我们需要的模型不但应该满足这两种需求,还应该能够支持健壮的UBIQUITOUS LANGUAGE(通用语言)。
从模型中获取用于程序设计和基本职责分配的术语。让程序代码成为模型的表达,代码的改变可能会是模型的改变。而其影响势必要波及接下来相应的项目活动。
完全依赖模型的实现通常需要支持建模范式的软件开发工具和语言,比如面向对象的编程。
单一模型能够减少出错的概率,因为程序设计直接来源于经过仔细考虑而创建的模型。程序设计,甚至是代码本身,都与模型密不可分。
要想创建出能够抓住主要问题并且帮助程序设计的单一模型并没有说的那么容易。我们不可能随手抓个模型就把它转化成可使用的设计。只有经过精心设计的模型才能促成切实可行的实现。想要使代码有效地描述模型就需要用到程序设计和实现的技巧。知识消化人员需要研究模型的各个选项,并将它们细化为实用的软件元素。
软件开发于是就成了一个不断精化模型、设计和代码的统一的迭代过程.
二、建模范式和工具支持
为了使MODEL-DRIVEN DESIGN发挥作用,一定要在可控范围内严格保证模型与设计之间的一致性。要实现这种严格的一致性,必须要运用由软件工具支持的建模范式,它可以在程序中直接创建模型中的对应概念。
面向对象编程之所以功能强大,是因为它基于建模范式,并且为模型构造提供了实现方式。从程序员的角度来看,对象真实存在于内存中,它们与其他对象相互联系,它们被组织成类,并且通过消息传递来完成相应的行为。许多开发人员只是得益于对象的技术能力——用其组织程序代码,只有用代码表达模型概念时,对象设计的真正突破之处才彰显出来。
实例:
我们可以把PCB看作是连接各种电路元件引脚的导体(称为net)集合。电路板上通常都会有成千上万个net。有一种叫做PCB布线工具的专用软件,能够为所有net安排物理布线,而不会使它们相互交叉或干扰。它的实现方法就是根据设计者规定的大量限制条件来限制布线方式以及优化路径选择。尽管PCB布线工具已经非常先进了,但是它仍然有一些缺陷。
工程师每次用8个、16个或者256个net组合成总线,这样布线工作就更易于管理了,不但提高了效率也减少了错误。问题是布线工具中没有类似于总线这样的概念。布线规则不得不应用于成千上万个net,一次处理一个net。
前面我们已经描述了领域专家思考问题时所使用的概念。现在需要将这些概念组织成模型,作为软件开发的基础。 用面向对象的语言实现上图的这些对象后,核心功能的实现会变得轻而易举。方法 assignRule()可以在抽象类AbstractNet中实现。而类Net中的方法assignedRules()则分配了其自身的规则以及类Bus的规则。当然,程序还需要大量的支持代码,但上面的代码片段已经呈现了脚本的基本功能。这个程序还需要导入/导出逻辑,我们可以将其封装成一些简单的服务。
我们还需要几个工具类:
现在,启动应用程序,用导入数据来初始化Net和Bus仓库
上面提到的服务和仓库都可以进行单元测试。更重要的是还可以测试核心领域逻辑。下面是对最核心的行为进行的单元测试(采用了JUnit测试框架):
程序应该有一个交互式的用户界面,可以列出所有总线,让用户逐个指定规则;或者可以向后兼容,从规则文件中读取规则。采用外观(façade)模式可以更容易地访问这些接口,如下代码是对应于上面测试的实现代码:
当然,如果只有一种操作(就像这个例子一样),那么基于脚本的处理方式可能也同样实用。但实际上,通常会需要20个甚至更多的操作。MODEL-DRIVEN DESIGN易于扩展,能够为规则的组合设置限制条件,还能提供其他的一些增强功能。
MODEL-DRIVEN DESIGN也为测试提供了方便。它的组件都具有定义完善的接口,可以进行单元测试。而测试脚本程序的唯一方法就是基于文件进行端到端的比较。
记住,这样的设计不是一蹴而就的。我们需要反复研究领域知识,不断重构模型,才能将领域中重要的概念提炼成简单而清晰的模型。
三、揭示主旨:为什么模型对用户至关重要
从理论上讲,也许你可以向用户展示任何一种系统视图,而不管底层如何实现。但实际上,系统上下层结构的不匹配轻则导致误解,重则产生bug。
MODEL-DRIVEN DESIGN要求只使用一个模型(在任何一个上下文中都是如此)大部分的设计建议和例子都只针对将分析模型和设计模型分离的问题,但是这里的问题涉及了另外一对不同的模型:用户模型和设计/实现模型.
当然,大多数情况下,没有经过处理的领域模型视图肯定不便于用户使用。但是在用户界面中出现与领域模型不同的“影像”将会使用户产生迷惑,除非这个“影像”完美无缺。
此外,如果以不同的方式来存储收藏夹,比如将其存储在一个数据文件中,这样收藏文件就可以有自己的规则了。这些规则很可能是应用于网页的命名规则。这又是一个单一模型。这个模型告诉用户所有关于网站的命名规则都适用于网站收藏夹。
如果程序设计基于一个能够反映出用户和领域专家所关心的基本问题的模型,那么与其他设计方式相比,这种设计可以将其主旨更明确地展示给用户。让用户了解模型,将使他们有更多机会挖掘软件的潜能,也能使软件的行为合乎情理、前后一致。
四、模式:HANDS-ON MODELER(亲身实践的建模者)
们总是把软件开发比喻成制造业。这个比喻的一个推论是:经验丰富的工程师做设计工作,而技能水平较低的劳动力负责组装产品。这种做法使许多项目陷入困境,原因很简单——软件开发就是设计。虽然开发团队中的每个成员都有自己的职责,但是将分析、建模、设计和编程工作过度分离会对MODEL-DRIVEN DESIGN产生不良影响。
-
如果编写代码的人员认为自己没必要对模型负责,或者不知道如何让模型为应用程序服务,那么这个模型就和程序没有任何关联。如果开发人员没有意识到改变代码就意味着改变模型,那么他们对程序的重构不但不会增强模型的作用,反而还会削弱它的效果。
-
同样,如果建模人员不参与到程序实现的过程中,那么对程序实现的约束就没有切身的感受,即使有,也会很快忘记。
-
MODEL-DRIVEN DESIGN的两个基本要素(即模型要支持有效的实现并抽象出关键的领域知识)已经失去了一个,最终模型将变得不再实用。最后一点,如果分工阻断了设计人员与开发人员之间的协作,使他们无法转达实现MODEL-DRIVEN DESIGN的种种细节,那么经验丰富的设计人员则不能将自己的知识和技术传递给开发人员。
如果把MODEL-DRIVEN DESIGN中密切相关的建模和实现这两个过程分离开,则会产生问题。
小结:
任何参与建模的技术人员,不管在项目中的主要职责是什么,都必须花时间了解代码。任何负责修改代码的人员则必须学会用代码来表达模型。每一个开发人员都必须不同程度地参与模型讨论并且与领域专家保持联系。参与不同工作的人都必须有意识地通过UBIQUITOUS LANGUAGE与接触代码的人及时交换关于模型的想法。
将建模和编程过程完全分离是行不通的,然而大型项目依然需要技术负责人来协调高层次的设计和建模,并帮助做出最困难或最关键的决策。
总结:
MODEL-DRIVEN DESIGN利用模型来为应用程序解决问题。项目组通过知识消化将大量杂乱无章的信息提炼成实用的模型。而MODEL-DRIVEN DESIGN将模型和程序实现过程紧密结合。
UBIQUITOUS LANGUAGE则成为开发人员、领域专家和软件产品之间传递信息的渠道。最终的软件产品能够在完全理解核心领域的基础上提供丰富的功能。
网友评论