探讨“数据库系统的面向对象开发” (0分)

>>All
TPersonGateway = class...
public
function Insert(lastname,firstname:string; numberOfDependents:integer):integer;
end;

function TPersonGateway.Insert(lastname,firstname:string; numberOfDependents:integer) : integer;
begin
Query.SQL.Text := 'INSERT INTO person VALUES:)key, :lastname, :firstname, :NOD)';
result := GetNextID();
Query.Params[0].asinteger := result;
Query.Params[1].asstring := lastname;
Query.Params[2].asstring := firstname;
Query.Params[3].asinteger := numberOfDependents;
Query.Execute;
end;
这样的设计方法,我们要实现的代码量是极其巨大的,我曾再一个系统中试过这样的
写法(大概是我水平菜了)我用一个RECORD来作为参数实现这样Insert的方法.
但是到了后面字段和表多了起来,写起来真的是非常麻烦,虽然有什么都是自己控制的
快感,但是又怀念起用数据感知控件的简单了...谁能解释一下么??
 
数据感知控件其实还是很好用的,仔细分析一下就会发现它是Observer模式在Delphi中的应用,之所以很多人反对用它,我想主要是两个原因:
1.两层模式下用户多的时候会建立大量数据库连接,严重限制支持的用户数量。
2.对数据集的任何操作往往都会影响到物理数据表,所以无法对数据集进行随心所欲的控制。

但是在三层模式下,通过ClientDataset可以解决第一个问题,并且提供了数据缓冲机制,提高了存取效率。
通过将ClientDataset当作MemDataset使用也可以自由的对数据进行控制,第二个问题也可以解决。

也许还有其他反对用数据感知控件的理由,提出来大家讨论吧。
 
我是觉得Table Data Gateway这种接口模式,有着其及其"麻烦"一面,
等于是数据感知控件完成了的工作我还要重新自己再做一次....:(
 
to kidneyball:
得花点时间好好拜读你所编译的文章,现在先暂时回答你的质疑。

1.在我所表达的对象之间的关系决不是数据关系的一种简单复制。数据库
对于所有的前端对象都是完全透明的。顶层对象向数据库提交数据时也不
仅仅是简单地将更新器中的内容送到数据库管理器而已。我们知道,前端
所看到的数据库内容一定是充满了冗余的数据,而后台的数据库则一定是
尽可能地消除了冗余的。对象体系是对数据库系统的扩展和补充。也就正
如你所讲:业务规则已经复杂到数据库的表间关系无法有效地表达。后台
的内容是紧凑的,而前台的内容是松散的。TDataset根本无法描述这种复
杂的表达。
我想知道,你所说的Data Mapper是因为何种因素的激发才向数据库提交
事务。在我看来,每个对象的事务应该是很细小的。例如,一单合同,往
往只可能涉及到客户、产品、产品规格、产品价格几个对象,不需要等到
更改三笔或更多合同时才向数据库提交。在多客户环境下,当你变动某个
产品价格时就有必要提交事务,然后立即通知其它客户。我的体系有时候
只是一个桥梁,一个方向是:向前,充分清晰地表达每个对象的外观,并
及时地通知由客户导致的数据的变动,由外壳充分地发挥;向后,按照客
户需求取出数据库中的、或者由其它客户提交的数据。另一个方向是:向
前,按照业务规则向数据库提交应由数据库表达的数据;向后,监视外壳
对数据的改动以及向数据后台的提交请求,并按业务规则进行审核。

说细致一点,我将数据对象分成两类:一类是编码类对象(或者说关键对
象,例如销售合同中的产品价格对象),这类对象的每一个对象是所有客户
都可能关心的,是建立、修改和删除其它对象所必须的。另一类是功能类
对象(或者说非关键对象,例如合同),这个对象类中只有极少一部分对象
实例被客户所关心,不需要客户端事先准备这个对象,某个客户查阅该对
象时都需要在登记册上建立一份登记,使用结束后应该向服务器注销这个
登记。前一类对象的每一次改动都必须在第一时间通知线上的每一个客户,
而后一类对象只通知在登记册中已登记的客户。这样的功能决不是TDataset
可以实现的,只有通过专门的对象方式实现。

2.间谍机制是这样的。每个对象一方面有多重的聚合类,例如合同价格对
象可能在三个地方被注册,一个是所属的产品及规格;一个是执行这项价
格的销售点;一个是所有的价格聚合。那么它可能被以上三个地方的原因
被释放,被释放的时候当然也必须在以上三个地方都注销。这样对象的管
理就成了重要的需求。我的处理是,以上三个地方都不能释放这个对象,
只有在顶层ObjectList中注销的时候才能最后真的释放这个对象。因而通
知间谍对象的变化也在顶层完成。如果有多重继承的话,专门设计一个类
用于对象的管理当然就方便了。
我不是说单继承实现不了,而是真的非常复杂。我已经实现,只是需要在
对象的祖先类TCxsObject中重载TObject的若干方法。

3.批量数据更新?如果有这样的需要,我认为数据库设计(进而影响对象
设计)一定存在某个漏洞。我的数据库设计中必须保证,每个事务对数据
库中单个表的影响不可能超过一行。当然,同时影响多个表是可能的。
你设想一下,有一万人,每人工资涨100元,我需要修改一万行记录,进
而可能需要服务器向客户端传送一万个包,然后客户端无端地处理这一万
个包。朋友,我不会忍心让服务器和客户端都承受这么重的负荷的。

如果你的工资采用等级制,显然工资额是不连续的。加入一个工资等级的
表。每个Employee对应到这个等级表中的一行,呵呵,提工资当然是好事,
改动那个等级表,别动employee表。如果你的工资采用计件制,那更简单
了,修改计件工价表中的某行。瞧!在客户端的内存中便多了一个工资等
级对象。当改动这个等级表中的某行时,客户端得到这个通知,修改相对
应的对象,这个对象通知顶层,由顶层向间谍发布,你的网页、ListView、
或者所有需要显示Employee工资的地方都会自动被刷新。

当然也有例外。在我的售楼系统中,可以一次向服务器提交整幢楼中的所
有房屋数据。我处理的方法是分解成多个事务,每个事务处理其中的一个
单位。幸好的我XML体系支持批处理,一次提交所有的房屋数据包,服务器
也就将多套房屋作为一个事务处理,然后发布给客户端。这样处理是有条
件的:一是可能合并的事务必须是同一类对象,而且有至少一个共同的属
性;二是事务的数量必须尽可能少。例如一次加入一个小区1000套房屋在
我这里不允许。

我喜欢这样一种策略:将流程分散处理,这样看起来似乎快一些,虽然集
中处理所花的时间肯定更少。例如当选中某个客户时,需要显示与该客户
所签合同额的总和。如果这个客户有3000个合同,统计这个总和可能需要
20秒的时间,客户会对这个操作有所不满。换一种方式,装入每份合同的
时候就将这份合同统计到该统计的地方。有可能客户永远都不需要统计这
个客户,必然浪费了统计单份合同所花的时间和存贮统计结果的空间,但
这种浪费是客户可以忍受的,甚至是毫无察觉的,万一客户需要这个统计
值的时候就会显得快多了。同样的道理,我不可能允许过大的事务。系统
设计的时候就需要避免这种可能。
 
to forss:
我觉得使用Table Module和用数据感知控件是不矛盾的。在Table Module的文章就多次提到
因为它内部的table可能要传递给数据感知控件使用,所以不能实现完全封装。封装的意图
就是要在内部数据发生变化的时候,保持界面不变。但既然数据感知控件很方便,而Borland
公司又不会在短期内改变TDataSet的结构的情况下(即使改变了,只要我们还是死撑着用
DELPHI7,也就没问题了),封不封装问题不大。这样的前提下,是完全可以使用数据感知控
件的。

至于用SQL语句编程量大的问题。我觉得除非你的程序完全是处理用户输入输出,不进行任何
业务上的计算,才可以完全不用SQL向数据库提交结果。那既然反正都要用,Table Data Gateway
仅仅是为了提供一个地方把所有的SQL都放在一齐而已。当然,由于它是面向表的,你可能
必须为每一个表重复结构相似的SQL,但我觉得在程序中动态改动一个SQL的表名不是那么好。
不过如果你一定要省下这份工夫的话,在Table Data Gateway的超类里做一个通用点的方法
就是了。如果将来你有需要根据为不同表改动这个方法,在子类把它里重载掉就行了。我觉得
封装的目的就是:1)保持界面稳定, 2)把可能在一次修改中要同时改动的东西放在一起(其
实这才是主要目的,保持界面稳定的目的就是:不能放在一次的,尽量不要一次都改)。只要
做到这两条,就可以了。
 
先弄个记号先,到时再来参与。
 
关于是否使用数据感知控件的问题:
我做这样的处理主要的原因是为了避免使用数据感知控件和TDataset。我自己写了一
套完全替代TDataset-TFields-TField的体系,完全改变了TTable/TQuery这种表达。
用过IBX吗?那里就有个与TDataset不兼容的高速的引擎,D6的dbExpress也似乎要改
变这种结构。但毕竟完全改掉它工程量太大了。也许D8或更高的版本会完全放弃,取
而代之的是一种完全面向业务对象的一种数据构造方法,直到可以采用更简单的方法
封装自己的事务请求。

重新自己再做一次数据感知控件完成了的工作,仅仅一次而已。如果你需要做若干个
项目,你也只需要做一次呀。如果你只做一个项目,那一次是个了不起的项目,重写
一次也值得的。
 
to barton
感谢你共享了你在实际应用中的经验。我觉得我正是缺乏这样的经验,因此许多事情都是在
纸上谈兵。在看了你在进一步的说明后,我大致能把握到你系统的架构了。但在这三个方面,
还是有进一步的问题。呵呵。

1) 你在第1)点主要是说明你的业务对象如何架构。我觉得你的对象采用这样的架构和分类
方法处理是得很好的。我之前说你的结构模拟了DATASET,是因为在整个架构上看,它采用
了第二层的聚合类管理第三层的业务类的方式。如果你的业务类间没有其他复杂关系,感觉
上就是一个TDataSet管理了一系列TFields的方式。而且如果第二层的聚合类扮演的角色不仅
仅是一个Container,还参与了业务逻辑的话,则它必须了解业务类的一些细节。这样这两层
之间的依赖度是很高的。 另外,关于更新器和业务类的分离,感觉上是把数据检验和回写数
据库分离到了更新器,而数据读入和其他业务逻辑则在业务类。这种做法的结果是业务类,
更新器,数据库三者之间谁也离不开谁。如果把业务逻辑和数据检验放在业务类中,数据读
入和更新放在更新器(现在就变成类似Data Mapper的角色了)中,会不会更好一点呢?

2) 我觉得这样复杂性的原因是在对象的联系中只有单向的联系。因为在A,B,C同时管理D的
时候,D中没有保留到管理器A,B,C的连接。所以假如A对D进行了一定操作,B,C并不知道。我
觉得在这里使用一个Observer模式是可以的,即D中也掌握到它的管理器的引用。在数据库
结构中,这样的一对多关系是无法在“一”的表中表达的。但在对象中,只要一个对象指针
表就可以轻易实现。这样在D中的方法被调用时(例如析构),它就可以通知A,B,C进行相关
操作(例如注销)。这个实现并不困难,只要在管理器和业务类的超类中加入一些代码实现
Observer机制就可以了。

3) 呵呵,你的答案和我想的很相似。我也觉得在实际应用中应该有避免一次性处理大量数据
的办法。但正如上面所说,我没有这方面的经验,不知道在实际中是否真能完全避免这种情况,
又或者即使有这样的情况,一记录一事务的做法也不会使效率低到用户难以忍受的地步。
 
恕小生愚昧,但数据库的事务处理的出现,不就是为了解决一次要同时更新多个纪录的需要
而产生的吗,一纪录一事务还能叫事务吗?
 
为了便于大家参考,我把Domain Model的部分也放上来吧。但是因为在Domain Model中不
涉及数据库操作了。源代码主要解决算法问题,没有太多的语言特征,所以我也不转换源
代码了。文中关于J2EE的部分,由于我对这个方面不熟悉,许多术语都不能很好的译出。
因此大家在看那部分的时候,仅仅用作参考就好了。而且请懂J2EE的朋友帮助修正。

Home Articles Talks Links Contact Me ISA ThoughtWorks

--------------------------------------------------------------------------------

Domain Model

--------------------------------------------------------------------------------

Build an object model of the domain that incorporate both behavior and data.
创建一个为业务领域整合了行为和数据的对象模型

图 http://martinfowler.com/isa/domainModelSketch.gif


在最坏的情况下,业务逻辑会变得非常复杂。 规则和逻辑描述了不同的情况和行为倾向。
面向对象正是设计来处理这种复杂的情况。一个Domain Model创建一个由互相关联的对象
所组成的网络。在这个网络中每个对象都表示一个有意义的实体。这些实体从非常巨大,例
如一间公司,到非常微小,例如订单中的一行。

How it Works

关于这个主题可以写出一本书来,因此我不知道从哪里开始才好。

把一个Domain Model放到一个应用中涉及到在其中插入一整个对象层。这些对象把你正在工
作的业务领域模型化。你会发现这些对象与业务中的数据很相似,而且这些对象包含了业务
中使用的规则。通常这些数据和过程被整合到一起使得过程与它们所工作的数据尽量靠近。

一个面向对象的domain model看起来常常与数据库模型很相似,然而它们之间其实是有很多
不同的。一个Domain Model混合了数据和过程,具有多值的属性(multi-valued attributes),
和可以使用继承。

因为业务行为经常变动,所以把这一层设计得易于修改,构建和测试非常重要。因此你会希
望使Domain Model与系统中其他层的耦合最小化。你会发现许多分层模式的指导思想就是使
domain model与系统其他部分的相互依赖尽可能小。

当你使用Domain Model时候,可能会使用几种不同的规模。最简单的情况是一个单用户的应
用程序,它把整个对象图(译注:object graph,应该是指图论角度上的图,即是读入所有
的对象并建立起它们之间的连接)都从磁盘中读入内存。一个桌面应用程序可能会以这种方
式工作,但在一个多层的IS应用中很会使用它。原因很简单,因为对象太多了。把每个对象
都放进内存消耗太多的时间和占用太多的内存。面向对象数据库的美丽之处在于它们在把对
象从内存和磁盘间来回移动的事情上做得很出色。

离开了一个面向对象数据库(OODB),你必须自己来做这件事。通常每个session都会牵涉到
读入一个由被这个session涉及到的所有对象组成的对象图。当然,这不会是所有的对象
(译注:指整个系统),而且通常不会是所有的类。因此如果你正着眼于一批合同,你可
能只会读入你正在处理的这些合同所涉及的产品对象。如果你仅仅在contracts和revenue recognition(译
注:见上面的关于例子背景的贴子)对象上执行一些计算,你可能根本不会读入任何产品
对象。你的数据库映射对象会精确地管理什么需要读入内存。

如果你在在对服务器的不同调用之间需要使用同一个对象图,你需要把服务器的状态保存在
某处。这是关于保存服务器状态的章节的主题。

一个对domain logic的普遍的担忧是过度膨胀的业务领域对象。当你在创建一个屏幕界面来
处理订单时,你注意到一些订单的行为仅仅在这个屏幕界面中被用到。如果你把这些职责放
进订单类里,你就面临着订单类会变得太大的风险。因为它会被这种仅仅被一个用例所用到
的职责所充斥。这个担忧使人们考虑一些职责是否具有普遍性,如果有,它应该被放进订单
类中;而一些特殊的方法,则应该放进面向特定用途的类中去。这些类可能是一
个Transaction Script或可能是界面本身。(译注:原文为presentation,这里根据上下文
译为界面。但实际上presentation并不特指用户界面,而可能是任何一种对数据的表示方
式)

分离面向特定用途的行为的问题是,它倾向于导致重复代码。从订单类中分离出来的行为很
难被找到,因此人们倾向于看不到它,于是重写一个。这种重复很快会导致更多的复杂性和
前后不一致。另一方面,我发现一个膨胀的业务对象并不如我想像中导致那么多问题。膨胀
也比预测中更少发生。如果膨胀真的发生了,它相对地容易被看到而且不难修正。因此我的
建议是不要尝试分离面向特定用途的行为。把所有行为顺其自然地放在合适的类中。当出现
膨胀时-如果它真的发生的话-再去修正它。

Java
当人们谈到在J2EE中开发Domain Model的时候,总是会情绪激昂起来。许多J2EE的教材和介
绍书籍都建议你使用entity beans来开发一个domain model。然而这种手段有几个严重的问
题。Entity beans是可以远程访问的(remotable),而因此建议使用一个粗糙的接口
(a coarse-grained interface),但一个Domain Model在你使用具有良好接口的对象
(fine grained objects)时才工作得最好。一个解决方案是使用session beans来包装
entity beans,这样entity bean就不能由远程访问了。虽然这是一个不错的做法,但在调
用entity bean的方法时仍然有额外的代价。

另一个entity beans的问题是-至少在J2EE第一版中-由容器管理的持久映射
(container managed persistence mapping)非常有限。这意味着你只能使entity beans一
一对应地映射到数据表,这种做法仅在Active Record风格的映射中才有效。你可以通过使
用由bean管理的持久性(bean managed persistence)获得更多地弹性,但如果你正在使用
BMP,不会远程访问你的entity beans的时候,它们的价值会显著地下降。

没错,entity beans处理内存缓冲。它减少了对Unit of Work(译注:另一种模式,用于管
理并发,它保存一份受事务影响的对象列表,协调数据库更新和解决并发性问题)的需求。
但它们同时非常难以排错,需要使用大量的类,在处理关联和继承的时候非常笨拙,增加了
项目的创建时间(译注:increase the build times,我真的不知道这个build中文怎么译。反正就是在delphi里的Project菜单里Compile选项下面那条选项里的那个build),而且通常会在解决一些性能问题的同时引起同样数量的其他性能问题。

一个替代方法是使用普通的Java对象。这个建议常常会造成惊讶,因为有数量令人吃惊的
人认为你不可能在一个EJB container中使用普通的java对象。我得出这样结论:人们之所
以会忘记普通的java对象,是因为它们没有一个响亮的名字。所以当我在准备一个演讲的时
候,Rebecca Parsons, Josh Mackenzie和我为它们起了一个:POJO (Plain Old Java Object,简
单的旧式java对象)。一个POJO Domain Model易于整合,build起来很快,可以在
EJB Container之外运行和测试,而且不依赖于EJB (也许这正是EJB厂商不鼓励你使用它的
原因)

我曾经见过用entity beans进行的很好的项目,前提是这些项目有着最复杂的业务逻辑而你
只有一个简单的到数据库的连接。大部分情况下,我更喜欢选择POJO的路线。

当然以上种种在EJB 2.0出现时将变得没有意义了。但是,因为EJB 2.0的说明书比一个即将
举行告别音乐会的人有着更多的“最终稿” (译注:反正就是很多啦),对我来说,现在
还很难作出任何评论。我真正希望的是,Domain Model能够尽可能从反复无常的开发平台中
独立出来。

When to Use it

如果说关于怎样使用Domain Model很难(difficult)是因为它是一个如此大的题目,那么关
于何时使用它则更难(hard)因为这类建议都既含糊又简单。一言而蔽之,选择的关键在于你
的系统中行为的复杂度。如果你面临复杂而且不停变动的商业规则,涉及到检验,计算和
派生...这是一个你使用对象模型来处理它们的良好机会。另一方面,如果你仅仅需要一个
简单的非空检验和一些累加计算,那么你最好把赌注押在Transaction Script上。

一个考虑因素是怎样运用正在使用业务对象的开发小组。学习怎样设计和使用一个Domain Model是
一个很具规模的练习 - 它把你带到一大堆关于"paradigm shift" of using object (译
注:不会译 :( )的文章中。当然,要习惯使用一个Domain Model需要实践和指导,但一旦
你习惯了它,我发现对于任何项目,很少人会回过头去使用Transaction Script - 除非它
是最最简单的问题。

如果你正在使用Domain Model,则我在数据库交互上的首选是Data Mapper。(译注:不要
问我:“你”使用什么关“我”什么事?原文就是这样写的)它帮助你保持Domain Model独
立于数据库。如果在Domain Model与数据库方案有分歧的时候,这是最好的解决办法了。

Example: Revenue Recognition (Java)

在我描述Domain Model中遇到的一个最大的挫败是:任何我展示的例子都必须足够简单,这
样你才能理解它;然而这种简单化隐藏了Domain Model的强大。你仅仅在一个现实的复杂业
务领域中,才可以真正体会到Domain Model的强大。

但即使这样的简单例子不能真正地告诉你为什么要使用一个Domain Model, 最低限度这个例
子能给你一个印象:它看起来象什么。因此在这里我使用了我在Transaction Script中使用
的那个例子-一个关于收入入帐的小事务。你可以比较这两个例子。


图1 : 使用Domain Model时这个例子的类图
http://martinfowler.com/isa/domainModelClasses.gif


有一件事必须在这里说明:每一个类,即使在这么小的例子里,同时包括了行为和数据。即
使最微不足道的Revenue recognition类也包括了一个简单的方法来找出这个对象的值(收
入的金额)在某个日期里是否可以入帐:

class RevenueRecognition...
private Money amount;
private MfDate date;

public RevenueRecognition(Money amount, MfDate date) {
this.amount = amount;
this.date = date;
}

public Money getAmount() {
return amount;
}

boolean isRecognizableBy(MfDate asOf) {
return asOf.after(date) || asOf.equals(date);
}

计算在一个特定日子里有多少收入可以入帐涉及到contract和revenue recognition类

class Contract...
private List revenueRecognitions = new ArrayList();

public Money recognizedRevenue(MfDate asOf) {
Money result = Money.dollars(0);
Iterator it = revenueRecognitions.iterator();
while (it.hasNext()) {
RevenueRecognition r = (RevenueRecognition) it.next();
if (r.isRecognizableBy(asOf))
result = result.add(r.getAmount());
}
return result;
}

在domain model中你会发现一件很普遍的事:多个类相互作用来完成一件即使是最简单的任
务。这常常导致人们抱怨在面向对象的程序中,你花费大量的时间逐个类地搜寻某段程序。
对于这种抱怨可以有很多方面的看法。当决定某件东西能否在某一天入帐的逻辑变得复杂起
来,而且其他对象需要知道这个决定时,它(指类合作)的价值才显现出来。把这个行为放
在需要知道两者的类中能够避免代码重复和减少不同对象之间耦合。 (译注:这段译得不
好,我也没能很好理解。因此原文放在下面,希望有朋友能帮忙修正)

A common thing you find in domain models is how multiple classes interact in order to do even the simplest tasks. This is what often leads to the complaint that with OO programs you spend a lot of time hunting around from class to class trying to find the program. There's a lot of sense to this complaint. The value comes as the decision whether something is recognizable by a certain date gets more complex and as other objects need to know. Containing the behavior on the object that needs to know both avoids duplication and reduces coupling between the different objects.

再看看计算和创建这些revenue recognition对象的代码,更进一步证明了这个关于大量小
对象相互合作的说法。在本例中计算和创建从customer开始,通过product对象传递到一个
strategy层次(继承树)中。strategy模式是一个著名的面向对象模式,它允许你把一组
操作整合到一个小型的类层次中。每一个product实例都连接到一个Recognition Strategy的
实例。这个strategy实例决定使用哪种算法来计算revenue recognition。在本例中我们有
两个Recognition Strategy的子类,对应两种不同的情况。代码的结构看起来象这样:

class Contract...
private Product product;
private Money revenue;
private MfDate whenSigned;
private Long id;

public Contract(Product product, Money revenue, MfDate whenSigned) {
this.product = product;
this.revenue = revenue;
this.whenSigned = whenSigned;
}

class Product...
private String name;
private RecognitionStrategy recognitionStrategy;

public Product(String name, RecognitionStrategy recognitionStrategy) {
this.name = name;
this.recognitionStrategy = recognitionStrategy;
}

public static Product newWordProcessor(String name) {
return new Product(name, new CompleteRecognitionStrategy());
}

public static Product newSpreadsheet(String name) {
return new Product(name, new ThreeWayRecognitionStrategy(60, 90));
}

public static Product newDatabase(String name) {
return new Product(name, new ThreeWayRecognitionStrategy(30, 60));
}

class RecognitionStrategy...
abstract void calculateRevenueRecognitions(Contract contract);

class CompleteRecognitionStrategy...
void calculateRevenueRecognitions(Contract contract) {
contract.addRevenueRecognition(new RevenueRecognition(contract.getRevenue(), contract.getWhenSigned()));
}

class ThreeWayRecognitionStrategy...
private int firstRecognitionOffset;
private int secondRecognitionOffset;

public ThreeWayRecognitionStrategy(int firstRecognitionOffset,
int secondRecognitionOffset)
{
this.firstRecognitionOffset = firstRecognitionOffset;
this.secondRecognitionOffset = secondRecognitionOffset;
}

void calculateRevenueRecognitions(Contract contract) {
Money[] allocation = contract.getRevenue().allocate(3);
contract.addRevenueRecognition(new RevenueRecognition
(allocation[0], contract.getWhenSigned()));
contract.addRevenueRecognition(new RevenueRecognition
(allocation[1], contract.getWhenSigned().addDays(firstRecognitionOffset)));
contract.addRevenueRecognition(new RevenueRecognition
(allocation[2], contract.getWhenSigned().addDays(secondRecognitionOffset)));
}

strategies的巨大价值在于它们提供了一个良好的插口来扩充这个应用程序。增加一个新
的revenue recognition算法只涉及到创建一个新的子类并重载calculateRevenueRecognitions方
法。这样使扩充应用程序的算法行为变得非常方便

当你创建产品对象时,你把它们与一个合适的strategy对象挂钩。在本例中,我在我的测
试代码中这样做:

class Tester...
private Product word = Product.newWordProcessor("Thinking Word");
private Product calc = Product.newSpreadsheet("Thinking Calc");
private Product db = Product.newDatabase("Thinking DB");

一旦所有东西都设置好,那么计算入帐不需要涉及到这些strategy子类的任何细节。

class Contract...
public void calculateRecognitions() {
product.calculateRevenueRecognitions(this);
}

class Product...
void calculateRevenueRecognitions(Contract contract) {
recognitionStrategy.calculateRevenueRecognitions(contract);
}

这种在对象与对象之间连续推进的面向对象的编程习惯不但把一个行为传递到最适合处理
它的对象中,而且解决了大部分的条件分支行为。你会注意到在这个计算中没有条件结构。
选择的路径在product对象被创建的时候就已经通过设置正确的strategy对象定好了。一旦
象这样设置好之后,算法只需要跟随这条路径。Domain Model在你面对一系列相似的条件
分支时工作得很好,因为它把这些相似的条件分支从代码中抽出来放到对象本身的结构中
去。这样就把复杂性从算法中移到了对象间的关系中。逻辑越接近,你会在系统的不同部分
发现越多的同样的关系网络。任何依赖于类型来计算的算法都可以使用这种特定的对象网络
结构。

你会注意到在这个例子中,我没有展示任何关于如何从数据库中存取对象信息的方法。这种
做法有一系列原因,首先把一个Domain Model映射到数据库总是稍微有点难度的,因此我不
敢在这里提供例子(译注:传统说法就是,因为这东西太复杂了,因为篇幅的关系,
所以......)。其次,在许多方面来看,一个Domain Model的主旨是隐藏数据库细节,这个
隐藏不但是对于上层系统,而且是对于使用这些Domain Model本身的编程人员。因此在这里
隐藏了数据库操作正是反映了在真实的编程环境中它看起来是什么样子的。



--------------------------------------------------------------------------------
 
to kidneyball:

1)的确如此呀,业务对象因为需要常驻内存,为了节省资源,我并不给它分配太多的功能。
更新器是应来自前端的请求而建立,提交完数据就撤消了,所以功能可以任意复杂,更新器
必须包含所有的业务细节。而第三层和第二层应该尽可能小,因为只提供单向的服务:从数
据对象到业务前端的服务。业务对象不能离开它的所有聚合对象和顶层对象,更新器离不开
它相关的业务对象。数据读入和更新是统一由顶层对象处理的,当然它交给专门的对象来完
成,这可能就相当于Data Mapper的数据收集功能吧。

2)我完全明白你所说的由于业务对象的某个聚合类更新某个对象时,通知它的其它聚合类。
D中当然知道它所属的所有聚合类,而且释放的时候也是由该业务通知其它聚合类的。但是
你想一下:从A中注销D时,通知B和C也注销D时,只可能从聚合类的List中删除它,不会释
放它,而只有释放的时候才会通知间谍而不是每次从List中删除这个对象时通知。相反,如
果聚合类从List中删除对象时就释放对象的话会导致释放一个已经被释放的对象。当初我也
象你说的这样进行处理,但无论如何难以解决越权访问错或者内存泄漏。这个机制花了我近
半个多月的时间才完全搞定。现在无论对象结构多么复杂都能够协调工作,内存泄漏也为0。

3)数据结构当然是系统框架设计的搞定的,这一问题不应该发生在写代码的时候。

to mycreatedream:
我对事务的定义是:对数据库中相关记录进行所有更新操作的总和。所以必须是相关记录。
当你添加一条订单的时候,如果这个客户是新客户,那么必须添加这个客户。这两次添加都
只涉及到一条记录,但如果客户添加失败,订单就无法添加,类似于某个存贮过程。这就是
我眼中的一个事务。我从前喜欢设计存贮过程,将一个事务在数据库一级搞定,现在我总是
在中间层搞定,比在数据库一级搞定快得多。
 
两天没来就已经有这么多帖子了,讨论得真是精彩,特别感谢kidneyball为我们大家翻译了
这么好的文章,我要抽时间好好看看。
好久没有见到这么高质量的帖子了,真是令人兴奋!
 
to barton:
1) 我明白了。就是把对象间互相影响的部分业务,以及一些主体的算法部分放在业务类中。
本个体的检验,提交等琐碎方法就放在更新器中。这的确是解决内存问题的一个好方法。请
你有空务必看一看我后来摆上去的关于Domain Model的译文。我心目中的一记录一对象模式
也是这样的,应该能自如使用各种设计模式,而且向编程人员隐藏了数据库细节。因为关
于Data Mapper的文章太长了,我可能要迟一些日子才能放上来,又或者到时这本书的中文
版已经出来了,我就不用班门弄斧了。但你看了关于Domain Model的描述,应该可以想像出
Data Mapper的职责了,和你的更新器类似,Data Mapper和业务类也是一一对应的(注意,是
类,不是对象),也就是说如果你有一个订单类,就有一个订单Data Mapper。不同的是:
Data Mapper知道业务类的细节,当你需要创建一个订单对象时,是向订单Mapper要求创建的。
而订单类不知道Mapper的存在,它只知道有个Finder的接口,是用来找相关产品的。这个接口
用产品的Data Mapper来创建相关产品。订单对象和产品对象的连接也由Mapper完成。当你
要把业务类回写数据库时,也是向Mapper发请求。反正,业务类除了算法什么都不管。(这就
是为什么关于Data Mapper的文章那么长了)。希望你能对这种模式说说看法。

2) 我觉得这里你似乎混淆了请求释放和真正释放。A是不能真正地释放D的(因为释放的D代码
不在A手上),它只能向D发出释放请求,由D来释放自己。因此你在D释放自己之前,完全可以向
A、B、C请求注销呀。(这里包括A,是因为A在要求D释放时,可以先不注销。等着D发来注销
请求时才注销)。(这纯粹是一个Observer模式了)这样无论谁需要释放D,都能协调好关
系。
 
1)不消说,我一定会认真拜读你编译的文章。有空的时候我再按你所说的模式试试。

2)当初我也是按你说的ObServer模式设计过的但没有成功。时间过的有些久了,我实在想不起来
当初是因为什么原因而放弃了。
 
我发现要想在C/S中实现上述的模式是不理智的.
只有在多层的系统中,上述方法才有真正的用武之地.
受益匪浅啊..:))
 
barton:
你的CXS的连接方式是什么样的呢?
 
在www.cnuml.com上有一个DBCLASSBUILDER的工具大家可以试看看.
可以节省自己写代码.不过我觉得这工具还很不完善..
 
对了.BARTON.你联系方式能留一个么?
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
I
回复
0
查看
577
import
I
顶部