如何组织业务逻辑(50分)

  • 主题发起人 kidneyball
  • 开始时间
K

kidneyball

Unregistered / Unconfirmed
GUEST, unregistred user!
摘自:Patterns of Enterprise Application Architecture
作者:Martin Fowler
2. Layering 请参阅我在 关于:探讨“数据库系统的面向对象开发”
(http://www.delphibbs.com/delphibbs/dispq.asp?LID=946305)中的贴子

3. Organizingdo
main Logic
最简单的储存业务逻辑的方法是使用Transaction Script(事务脚本)。一个Transaction Script
本质上就是一个procedure(过程)。它包括了从界面获得输入信息,对它们进行检验和计
算,把数据保存到数据库,调用外部系统中有用的操作,并且向表达层(用户界面)提供更
多的数据(有可能需要更多的计算来帮助组织和整理回应的数据)。它的基本的组织思想就是
为每一个用户希望完成的动作(Action)提供一个单一的procedure(过程)。因此我们可以把
它看作是动作(或商业事务)的脚本。当然,它在源代码中并不一定就是一个单一的
procedure,它的各部分可能会被拆分成子程序而这些子程序有可能被不同的Transaction
Script公用。但即使这样,最终的驱动力仍然是为每一个单一动作而写的procedure。这样,
一个零售系统可能包括了确定购买、添加商品到购物车、显示分发状态等等的Transaction
Script
Transaction Script的优点在于:
1) 大部分开发者都能理解这种简单的过程化模型
2) 它与一些简单的数据源Layer,例如Row Data Gateway或Table Data Gateway结合得很好。
3) transaction(译注:估计是指数据库中的transaction)的边界非常容易定义:实际事务
开始的时候打开一个transaction,结束的时候就关闭它。这使得后台工具能非常容易地根
据实际情景完成这些操作。
遗憾的是,它同时也有不少的缺点。这些缺点随着业务领域逻辑的复杂性增加而越来越明
显。经常会因为几个Transaction需要做相似的事情,而导致重复代码的出现。虽然这种情
况能通过重构一些公共子程序来稍稍改善,但仍然会有很多重复代码难于移除和难于发现。
这样,应用程序最后将会变成一个没有清晰结构的杂乱的子程序网。
当然,复杂的逻辑正是引入对象的好地方。处理复杂的业务逻辑的面向对象方法是使用
Domain Model。Domain Model的主要组织方法是建立一个由我们的业务领域中的名词所组成
的模型。这样,一个租赁系统就应该有租约、资产等各种类。处理数据检验和计算的逻辑
就应该放到这些Domain Model中去。一个货物分发(shipmeng)对象应该包括计算送货费用的
逻辑。当然,计算帐单子程序仍然有可能存在,但这个procedure应该被一个Domain Model
中的方法代理使用。(译注:Delegate,即不直接调用这个子程序,而通过业务对象的方法
来间接调用。也就是一些朋友所说的“封装”,但我觉得这种说法与encapsulate混淆,因
此倾向于使用“代理”的说法)
使用Domain Model来取代Transaction script,正是现在许多面向对象编程人员津津乐道
的改革的核心问题。这时,每个对象仅包含与自己相关的部分逻辑,而不是一个procedure
囊括了一个用户动作的所有逻辑。如果你不习惯于使用Domain Model,那么学习如何使用
它会非常令人灰心,因为你得在一个又一个的对象之间寻找你想要的行为(译注:behavior,
指一个对象行为的相关程序)。
要用一个例子来说明两者之间根本的不同点是很难的,但在关于模式的讨论中,我会尝试
用两种不同的方法来实现同一个业务逻辑。要看出不同点,最容易的方法是分别看这两种不
同手段的sequence diagram。在这个例子中(http://www.delphibbs.com/delphibbs/dispq.asp?LID=946305),
根本的问题在于不同类型的产品对某个合同会有不同的算法来计算收入。从事计算的method必须
确定某个合同对应什么产品,应用正确的算法,然后创建一个收入对象来保存计算结果。
(为简单起见,我忽略了数据库的交互问题)
Domain Model的价值在于一旦你习惯了它,那么就可以运用很多技术来帮助你有条理地处
理复杂的逻辑。正如在例子中,当越来越多计算收入的算法出现的时候,我们可以简单地
通过加入新的收入策略对象来加入这些算法。如果你使用Transaction Script,我们只能
向脚本中加入更多的条件分支。一旦你的喜好开始象我这样倾向于使用对象,你会发现你
会倾向于选择Domain Model,即使对于一些相当简单的情况也会这样。
使用Domain Model的代价在于它和它对应的数据源Layer的复杂度。对“胖对象模型”不熟
悉的人们需要时间来适应“胖Domain Model”。开发人员常常需要花费几个月的时间参与
使用Domain Model的项目之后,才能把思维模式转变过来。但是,一旦你熟悉了你的第一
个Domain Model,你很可能就会从此迷上它,变得非常倾向于使用它们——这就是象我这
样的对象狂热者的来历了。但是,有少部分的开发人员似乎并不能适应这种转变。
但即使你已经习惯了使用Domain Model,你还必须处理数据映射的问题。你的Domain
Model规模越大,你把它们映射到关系数据的机制(通常是使用Data Mapper)就越复杂。
一个完善的数据源Layer比较象一种固定投资。你付出一定的金钱(如果你购买)或者时间
(如果你自己开发)来得到一个良好的数据源Layer,但一旦你手上有一个,你就能用它做
很多事情。
还有第三种架构业务逻辑的方法:Table Module。初初看去,Table Module和Domain Model很
相似,因为它们都具有关于合同,产品和收入的类(在例子中)。根本的差别在于Domain Model对
数据库的每个合同记录都有一个合同对象实例与之对应,而一个Table Module只有一个对象
来处理所有的合同。Table Module是设计来与Record Set一起工作的。这样,一个合同Table Module
的客户对象会首先向数据库提交一个查询来建立一个Record Set,然后它会创建一个合同对
象并把这个Record Set作为参数传递给它。这样,客户对象就能调用合同对象里的操作来
完成各种事情。如果它希望对某个特定的合同进行操作,则它必须传递一个ID给合同
Table Module对象。

 
从许多方面来看,Table Module是Transaction Script和Domain Model的折中方案。围绕数
据表来组织业务逻辑比单纯地使用procedure具有更良好的结构,同时更容易发现和移除重
复代码。但是,有许多能在Domain Model使用的能优化结构的技术不适用于Table Model,
例如继承,策略模式,和其他的面向对象模式。
使用Table Module最大的好处是它与系统架构中其他部分的融合程度。许多图形用户界面都
被设计成基于SQL查询结果的Record Set来工作。由于Table Module也是基于
Record Set工作的,你可以很方便地运行一个查询,在Table Module中处理查询结果,然
后把处理后的数据传递给GUI用于显示。你还能使用Table Module中进行后续的检验和计算。
有许多平台,特别是微软的COM和.NET使用这种开发风格。
至此,我以互斥的态度谈及了三种组织业务逻辑的风格。虽然单独地分别讨论它们有助于
理解,但应用程序中通常把Transaction Scripts和Domain Module结合起来使用。(其他
组合也有可能,但很少见) 使用混合方法时,问题在于有多少行为应该放到脚本中而有多
少该放到业务对象中。这并没有明确的分界,但三种值得讨论的情况是:Transaction Script占
主要地位,Domain Model占主要地位,和controller-entity mix
在Transaction Script占主要地位时,你把大部分的业务逻辑放到Transaction Scripts中,
同时你把一些公用的行为摆到相关的业务对象中。因为这些业务对象非常简单,他们通常
和数据库一一对应,你能够使用一个很简单的数据源Layer,例如Active Record.
在Domain Model占主要地位时,你把大部分的逻辑放到业务对象中,只把一些协调用的代
码放到Transaction Script中。一个折中的方法是controller-entity风格。它的特点是
把任何与特定事务或用例相关的逻辑放到Transaction Scripts中,这被称为controller。
这里的controller与MVC或Application Controller中的Controller不同,因此我使用
use-case controller(用例controller)这种名称。被一个以上用例用到的行为就放
到业务对象中,这被称为entities.
虽然controller entity方法使用得很普遍,但我并不喜欢它。它其中的use-case controller部
分,就如其他的Transaction Script一样倾向于产生重复代码。我的观点是如果你最终决
定使用Domain Model,你就应该一气呵成把它作为主要结构。唯一例外的情况是如果你已
经用Transaction Script加Row Data Gateway的组合开了个头,那么你可以把重复代码移
到Row Data Gateway中。这样你就把Row Data Gateway变成了一个使用Active Record模式
的简单Domain Model。但我只会在要改进一个已经出现问题的设计时才会这样做,正常情
况下,我根本就不会这样开头。
那么,怎样才能从这三者中作出选择呢?这个选择并不容易,但它很大程度上依赖于你的
业务逻辑有多复杂。图四是一张非常不科学的图示并在我演讲时令我非常恼怒——因为它
的XY轴上甚至没有标尺。但是,它有助于把我对这三者的比较形象化。当业务逻辑很简单
的时候,Domain Model并不吸引,因为它难于理解和复杂数据源的开支使得许多额外增加
的投入无法取得回报。但是,随着业务逻辑的复杂度增加,其他方法将会很容易碰壁,因
为添加新功能的困难会指数级地增长。
你所面对的问题在于确定你手中应用程序的复杂度在X轴的什么地方。遗憾的是,并没有
精确的方法来测量,所以你能做的唯一替代方法是找一些有经验的人来进行初期需求分
析并为你判定该如何做。
有一些方法能稍微改变这些曲线。熟悉Domain Model的TA小组会降低使用Domain Model的
初始成本。但无论如何,由于数据源复杂度的关系,它总不会低于其他两种方法的初始开
支。但这个小组越好,我就越倾向于使用Domain Model。
Table Module的吸引程度取决于你的环境对Record Set结构的支持程度。如果你在使
用的环境类似.NET或Visual Studio那样具有许多支持Record Set的工具,则Table Module
变得非常有吸引力。实际上,我看不到任何理由去在.NET环境中使用Transaction Script。但是如果环境中没有特别为Record Set而设的工具,则我不会考虑Table Module。
一旦你下了决定,并不是说你就不能改变它,但改变需要相当的技巧。所以在着手之前考
虑好走那条路是非常重要的。如果你发现你真的走错了路,那么若你是从Transaction Script开
始的,就应毫不犹豫地把它重构为Domain Model。但若你是从Domain Model开始的,那
么把它向Transaction Script的方向修改通常是不值得的——除非你能够简化你的数据源Layer
 
看不出想说什么,但是收藏。^o^
 
看来我还是把整本书都译了,做成一个中英对照的CHM文件吧。
正在翻译中,请大家多多捧场。
 
支持!!!
 
支持:)
 
立正,向kidneyball敬礼
 
to kidneyball,翻译进度怎么样了??
强烈支持呀!
 
缈昏瘧濂藉悗锛岀粰鎴戝彂涓
 
打心底支持!!!!!
 
顶部