转载《面向对象软件开发和过程(七)》

业务建模

林星 ([email protected])
2003 年 12 月

业务建模是OOAD的重要组成部分,简单的说,业务建模就对业务领域问题进行结构化的描述。这个描述将会直接指导最终生成的软件,业务模型是否具有扩展性,业务模型是否能够正确的反映需求,都将影响最终软件的质量。

1. 业务建模

1.1 为什么要业务建模?

我们把业务建模这个概念放在了最后的部分,因为面向对象是业务建模的基础。面向对象是一种用计算机语言模拟现实生活的技术。而传统的语言是基于时序的,是计算机观点的语言,和人们熟悉的社会观点是不同的。在软件发展初期时,这并不是什么很大的问题,但是当软件规模越来越大,变化的速度越来越快的时候。人们发现两种观念有了冲突。例如,订单这个对象是人类社会的一个普遍的商业名词,它是相当稳定的。所不同的只是处理规则有所不同,但在传统的语言中,订单的名词并不是关心的重点,关心的重点反而放在了订单的处理时序上。偏偏这部分的处理是不稳定的,所以就引发了变化的问题。而面向对象采用现实世界系统的思考方式,侧重于建立订单这个类型,并构造订单类型的体系,然后再建立规则。所以,他和现实世界的变化频度是基本一致,变化起来也就比较容易。

我们之前曾多次讨论了面向对象的抽象。我们知道,面向对象用于描述现实世界,他的抽象级别还太低了。所以,后来有了组件技术。组件技术提倡松耦合、粗粒度的构建方式,但是他在本质上和面向对象并没有什么太大的差异。而业务建模的关键,就是如何利用面向对象技术来描述现实世界。

1. 2 业务建模和数据库建模

很多人都经历过基于数据的应用,在需求分析完毕后,立刻建立数据模型,基于数据模型构建应用。这是这种应用的典型做法。利用先进的工具,能够达到很高的开发速度,所以这种方法在CS方式的软件中被大量采用。这种方法的基本思想是使用数据库来表示业务模型。但是我们需要深入的来思考这个问题,是不是数据库能够完全胜任这一点呢?我们就最广泛使用的关系型数据库来进行讨论。

我们知道,关系型数据库的基本原理是集合论。通过定义二维表和使用笛卡儿乘积定义二维表之间的关系来完成设计。这和我们现实生活有一定的类似之处,这是很自然的一件事情,因为数学本身就来源于生活,而软件的基础理论是数学。对于数据来说,它本身并没有很多意义,你能够说明下面这个字符串的意义吗?


 "香港XX贸易有限公司" 

答案是不能,但是通过数据库的表名称和表字段的定义,我们可以大致推算出他的应用范围:


 表名: 客户列名: 中文名称"香港XX贸易有限公司" 

我们知道,这个字符串表示了客户的中文名称。但是遗憾的是,这种的定义仍然不够精确,应该承认,它有用,但是意义并不大,对业务没什么帮助。真正对业务有帮助的描述应该是:


 "香港XX贸易有限公司"是本公司的10大客户之一,我们需要给其比较好的优惠。 "香港XX贸易有限公司"的应付款已经到期,必须降低该客户的信用评级。 

这些事情是数据库没有办法做的。当然,现在数据库技术的发展一日千里,很多数据库都用不同的技术来帮助客户在数据库上进行业务规则的处理。但是数据库不擅长处理的东西,并没有必要强制在数据库上实现。这些事情,应该由面向对象来进行处理。因为面向对象是数据和操作的完美结合,但是面向对象有一个缺陷,就是持久化的能力很低,所以,数据库仍然是面向对象的最佳合作伙伴。因此,我们有了ORMapping技术,后来又有了JDO的技术。这些都是为了强化面向对象和关系型数据库之间的沟通而设计的,因为他们两者的设计理论不同,所以天生存在很大的隔阂,非得借助中间层次的帮助。而在敏捷方法论中,也专门有研究数据库方面的技术,这表现了数据库技术的重要程度。

1.3 共性和差异性

业务建模中,寻找、区分不同事物之间的共性和差异性是非常关键的。两个业务实体,之间不可能只有共性,如果那样的话,那就只会剩下一个业务实体了;反过来,他们之间也不可能只有差异性,世间万物,皆有关联。退一万步说,他们至少都属于某个组织的业务实体,都是业务实体,这也是他们的共性。在业务模型中进行正确的分析,能够为之后的面向对象建模打下坚实的基础。如果这一步做的不好,你会发现,你是在使用面向对象的技术来编写面向对象过程的代码。

1. 4 不要把表和业务实体混为一谈

对于业务建模而言,业务实体是非常重要的。几乎所有的业务模型,都表现为不同业务实体之间的某种协作关系。Jill Nicola在他的业务对象建模和协作模式一文中,提出了4种的协作类型,并演化出12种协作模式。应该说,他的用意,是对业务模型进行分类,其目标和GOF的设计模式一书是类似的。但是业务模型面对着的,是大千世界中的各种现象,要将它们归类整理,谈何容易。所以,Jill Nicola也仅仅是只能够提出一个思路,但这对业务建模者来说,是很有价值的。

很多开发人员容易把表和业务实体混起来,但它们之间并没有什么联系。正如上文讨论业务建模和数据库建模的关系一样,数据库只是实现业务模型持久化特性的一种手段。把业务实体一对一映射到表,有时候是对的,有时候则不对。业务实体的设计一定是从需求开始,到领域模型,而表设计是基于数据库的处理原则。一个订单实体,在数据库中可能会有两张表,一张是订单、一章是订单子目。很多时候,业务实体的粒度要大于表,但也不一定,例如在ORMapping中,就常常把一个继承树映射到单个表中。所以它们两者之间没有必然的联系。在业务建模时,就单纯的考虑业务实体的设计,在数据库建模时,就单纯的考虑表设计,不要混淆两者。

1. 5 警惕CRUD类型的设计

这个问题产生的本质是数据库设计和业务模型设计的冲突。从数据库的观点来看,任何一种业务都不外乎新增记录、修改记录、删除记录、查询记录。所以象PowerBuilder那样的工具,能够凭借优秀的数据窗体的处理能够里雄踞一方。但是这个观点是业务建模中十分忌讳的,为什么呢?对任何一项业务来说,对记录的处理都不是问题的根本。新增一条记录必定伴随着一项业务的发生,例如销售了一件产品,新注册了一个用户。这种对用户来说才是有意义的,用户并不关心记录的处理,他们只关心业务问题。所以,当你的业务模型如果充满了CRUD的设计,这说明你看待业务模型的角度有问题,你仍然停留在从关系数据库看待业务的层次上。

在软件开发过程中,这个问题需要引起注意。单纯的CRUD方法往往意味着需求没有被正确地理解,模型可能遗漏了需求。我们想象一个新增订单记录的AddOrder方法,一个订单需要填写哪些要素?新增订单时是否需要检查客户的信用记录?是否需要检查产品的库存?现实生活中的业务规则很复杂,不可能如此的简单。那么,现在你可以检查看看你的系统,是否存在着大量的CRUD设计?

1. 6 尽可能使设计贴近现实世界

这一点并不是绝对的,例如,理论上人的生日是不能够调整的,但是在软件的世界中,需要提供修改生日的方法来修正录入的错误。这个问题可以这样理解。对于软件系统来说,并不存在创建人这样的业务模型,这是生命的秘密,不是软件系统能够模拟的。真正有意义的业务模型应该是建立档案,招聘新员工这样的业务模型,因此修改档案和修改员工资料都是允许的。

1. 7 显式接口和隐式接口

想象一个用户管理模型提供特定客户的接口,不同的搜索条件使用不同的参数:


 Customer getCustomer(int customerID) Customer getCustomer(string customerName) 

面向对象能够很好支持重载特性,因此,这种方式声明的接口是相当优雅的。对用户来说,接口是统一的,编译器能够提示你使用合适的重载方法,并帮助你检查类型。问题在于,有一些条件具有相同的参数类型,这就和重载的要求背离了,例如:


 Customer getCustomer(string customerName) Customer getCustomer(string customerAddr) 

这个时候,我们就需要改变一下思路,把接口变成显式定义的:


 Customer getCustomerByName(string customerName) Customer getCustomerByAddr(string customerAddr) 

当方法数量比较少的时候,这种显式接口也很有优势,因为方法名清晰的表示了其意图,用户不需要太多的帮助就能够使用适当的方法。所以我们这里看到方法名的设计是很重要的,而在团队内部保持方法名风格的一致同样是很重要的。

如果获取客户这个方法有十几中的过滤条件,我们发现,显示接口的方法是有问题的,方法列表太多,用户难以使用,而选择逻辑也开始分散,不容易进行扩展。如果说,一个业务方法复杂到了这种程度,我们认为值得多投入一些时间对方法进行改进。我们的思路是把显式接转换为隐式接口,并把过滤条件这个概念进行抽象。请注意我们这么做的理由,方法的目的是相同的,都是获取客户,但是它们的过滤条件不同,所以我们为了提高抽象性,我们把过滤条件的共性表述出来,这就是过滤条件本身。这就是面向对象的威力,不论是任何的事物,都可以在某个程度上进行抽象。所以我们引入了一个新的过滤对象,CustomerFilter。注意,我并不把它命名为Filter,因为我认为过滤是一个通用的特性,很多的对象都具有这种特性,所以,我希望在条件成熟的时候能够从CustomerFilter抽取出一个基类出来,能够将其用于OrderFilter的设计。所以我的命名为将来留有余地,但是我现在并不实现它,因为重构和自动测试技术能够帮助我很快将Filter实现出来,对这一点我毫不担心。罗里罗嗦讲了这么多,我们来看看最后的方法是什么样子的:


 Customer getCustomer(CustomerFilter customerFilter) 

显示接口和隐式接口在设计中大量存在,你无法下结论说哪一种方法比较好,任何一种方法都有其存在的价值。我们做的是根据情况来进行分析,以决定使用哪一种方法。

那么,过滤器是如何实现的呢?过滤器实现中最重要的是就是过滤规则由谁实现。同样的,这里的设计同样是一种权衡。最标准的做法是使用正则表达式,例如"Name Like M*",这样,过滤器本身只是提供了表达式的叙述,至多再进行语法的检查而已,接受过滤器的类需要负责实现过滤功能。很多的系统中都提供了类似的操作,因此,设计时可以充分的利用它们。

在Martin Folwer的分析模式一书中,提到了另一种过滤器的设计思路,把过滤规则封装在过滤器中,由于过滤规则封装在过滤器中,所以过滤器必须提供一个查询方法:


 isIncluded(Customer aCustomer) 

每一次,提供Customer的类需要向过滤器询问,这个客户在过滤器中吗?过滤器回答。这种做法的好处是提供了更大的灵活性,可以实现非常复杂的过滤规则。但是这种过滤规则会生成复杂的过滤器类,这并不是一个好消息。你可以使用简单的Case语句,或是子类化等方法来实现过滤策略。

2. 规范

业务建模不存在一个规范,不同人、不同问题,最后的设计结果一定不同。目前大家比较接收的方式是通过模式的形式来记录、讨论一个业务模型。例如,我们考虑银行的操作人员处理一项业务,这种过程活动就可以使用经典的Participant-Transaction模式来进行描述:

某种角色处理了某个交易,而这个交易和相关的人员都需要记录下来,这种情况适合采用Participant-Transaction模式来解决。模式针对某个特定环境的特点使之很适合用在业务建模中,业务建模的规范确定之后,还可以给出特定技术平台下的实现,从而达到规范代码的目的。

3. 组织

业务建模的组织问题和重用的组织问题极为类似。

4. 过程

最初的模型就保持一个稳定的状态是不太现实的。所以,把大量的时间花费在完善业务模型上并不是一个好的做法。在项目开始的时候,参与项目的双方的配合程度低,相互不能够理解对方的工作,开发方无法理解要处理的问题域,需求方也弄不清楚软件的最后模样。水源已经脏了,下游再怎么清理也是有限的。在这种情况下,你认为领域模型能有多大的稳定性?

但是,如果业务模型不能够快速的确定下来,就会对后续的活动产生影响。所以,在开发过程中,对原先设计和代码的重构能力是非常重要的。具体的说,就是在需求变化或更加明确的情况下,需要对原先错误或不足的设计及代码进行修改,但在添加或修改原有软件的同时,能够保证原先的功能和质量不受影响。所以,为什么迭代的思路不容易贯彻,就是因为后续的变更对之前的架构容易产生影响。而在一个应用中,业务模型往往是整个软件的核心,所以软件重构的能力直接表现为业务模型重构的能力。

对于我们来说,业务模型一方面要尽可能的稳定,少变化,另一方面我们又要求业务模型能够进行快速的变化,以适应需要。所以业务模型设计对过程提出了几点要求:

4.1 针对业务模型构建自动化测试网

我们说过,业务模型是软件设计的核心。任何底层的问题都会反馈到业务模型上来,而业务模型和需求非常的接近,因此测试用例的设计难度比较低。我们知道,测试工作最难的就是如何权衡测试工作量和软件质量的关系,把测试的力量集中在业务模型上,能够有不错的效果(这并不是说其它的部分不需要测试,单元测试的主要思路是测试一切可以测试的东西)。但是这种测试也需要付出额外的代价,最典型的是测试环境的建立,由于是业务模型测试,不可能象单元测试那样容易,所以需要有周全的测试数据和测试计划。这项工作应该放到业务模型的类设计中,成为进度的一部分。如果是业务模型的关联性很大,还要从整体上考虑。

4.2 保持设计的统一

业务模型往往是关联性很强的,而这个趋势将会越来越明显。为什么ERP会如此的复杂,就是因为业务关联性非常的复杂。所以,尽可能在业务建模活动中投入少而精的力量,保持统一的设计风格。这一点很重要,如果设计思路不统一,会对业务模型产生很大的影响。

4.3 审核

由于业务模型的重要程度,因此建议在每一次迭代初期加强对业务模型的审核。尤其是评估业务模型变化对软件设计的影响。

4.4 进度估算

同样的,进度估算同样可以以业务模型为基础进行。因为业务模型的粒度适中,所以进度的分配和统计相对比较容易进行。

参考资料

Design Patterns: Elements of Reusable Object-Oriented Software. Gamma, E. Helm, R. Johnson and J. Vlissides Addison-Wesley 1995. 中文版:《设计模式:可复用面向对象软件的基础》,李英军等译,机械工业出版社2000 年9 月。

J2EE核心模式 Deepak Alur, John Crupi, Dan Malks 牛志奇等译,机械工业出版社。

Effective Java Programming Language Guide. Joshua Bloch 2002 中文版:《Effective Java》潘爱民译,机械工业出版社2003年1月。

[Kel+96b] Wolfgang Keller, Jens Coldewey: Relational Database Access Layers: A Pattern Language, Proceedings PLoP '96, Allerton Park 1996.

[BMR+96] Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, Michael Stal: Pattern-Oriented Software Architecture - A System of Patterns; John Wiley & Sons Ltd., Chichester, England, 1996; ISBN 0-471-95869-7

[Fow97] Martin Fowler: Analysis Patterns- Reusable Object Models; Addison-Wesley Publishing Company, Reading, Massachusetts , 1997; ISBN 0-201-89542-0

[Fowler, roles] Fowler, Martin. Dealing with Roles, http://www.awl.com/cseng/titles/0-201-89542-0/awweb.htm

[Fer00b] E. B. Fernandez and X. Yuan. "Analysis patterns for the order and shipment of a product", Procs. of PloP 2000. http://jerry.cs.uiuc.edu/~plop/plop2k

[Fer00c] E. B. Fernandez. "Stock Manager: an analysis pattern for inventories", Procs. Of PloP 2000. http://jerry.cs.uiuc.edu/~plop/plop2k

Refactoring To Patterns Joshua Kerievsky 2002

关于作者

林星,辰讯软件工作室项目管理组资深项目经理,有多年项目实施经验。辰讯软件工作室致力于先进软件思想、软件技术的应用,主要的研究方向在于软件过程思想、Linux集群技术、OO技术和软件工厂模式。您可以通过电子邮件 [email protected] 和他联系。

你可能感兴趣的:(转载《面向对象软件开发和过程(七)》)