net生活

博客园 首页 新随笔 联系 订阅 管理
  30 Posts :: 0 Stories :: 496 Comments :: 26 Trackbacks

 本不想对这个图书馆再掀话题﹐看了亚同志的重构图书馆惊魂夜﹐觉得还是有必要完整地解释一下图书馆与领域模型﹐毕竟这个问题由我而起﹐善终一下吧。

 

首先把图书馆系统的背景说明一下吧﹕公司每个成员通过局域网登录图书管理系统﹐然后预借书籍﹐图书管理员收到预借信息后﹐核准借阅﹐并通知借书人前来领书﹐告知相关事项。

 

领域模型的价值不在于它的设计优美(它只是一些对象﹐最重要的也就是对象之间的关系)﹐而在于它体现了系统的核心价值。什么是系统的核心价值呢?我想我们的图书馆系统和华尔街的一个商业系统本质的区别应该不是用了什么语言﹐OO还是过程﹐用了什么数据库。因为这两个系统使用什么语言﹐采用什么方式﹐利用什么DBMS﹐都是不能本质区别这两个系统的价值的。

 

那什么才是系统真正的价值呢?

那就是﹕系统能为使用提供什么服务﹐以及提供的质量(即如何服务﹐这也是系统内部的我们要做的事情)

 

那什么体现的系统的服务呢?系统的运行方式﹐系统的运行过程﹐系统的业务逻辑。

 

以数据库为中心的开发方式﹐将所有业务逻辑都放在了数据库﹐并结合系统的代码来保证业务逻辑的实现。

而面向对象﹐则其表现点则是直接在对象本身上﹐在于对象之间真正的交互过程﹐结果也是保留在对象的属性和对象与对象的关系中。

从以上方式可以看出﹐无论以数据库为中心的开发如何的OO﹐如何多的设计模式﹐架构体系如何优美﹐它始终离不开数据库。它的OO只是一种手段﹐而非目的。(大家别激动﹐我始终没有提这种方式不好﹐只是实现系统的两种方法而已)

面向对象则不同﹐它将逻辑直接存在于对象上﹐这与现实情况是吻合的﹐这也是面向对象引起这么大热潮的根本原因(我想﹐不明白这一点﹐您会在ORM,AOP等等方面不知其所以然的)

 

领域模型是一种思维﹐是一种方法,是在系统分析阶段使用﹐而不是程序员在自己的代码中进行纯设计时的工具。我们不是为了OO而领域﹐不是为了最终要新增数据库而领域﹐这也是为什么在没有理解领域模型本质时﹐使用它进行片断式的代码编写﹐得不到任何好处的原因。想想你在编码中弄出的那些用户﹐图书对象﹐它在系统中的位置是什么?您不能否认它就是为了让新增数据库时更方便吧?

 

领域模型的建立决不是像如何实现某些纯软件逻辑而进行的纯软件设计。(我这里提出的纯软件设计﹐指的是如何在设计中体现扩展性﹐灵活性﹐可维护性而进行的设计)。而是为了能够分析系统提供的服务而产生的一种思维结果。

 

 

系统分析员在接手一个系统后﹐首先要做到的事情就是得出系统的服务和服务场景。也就是我们经常所讲的用例(use case)

可惜的是﹐很多使用者一直将用例的作用和价值没弄清楚。我想如果没有真正理解用例的作用时﹐您的用例分析将会一直停留在和别人Demo系统之上﹐提供一個漂亮的圖形好說話而已﹐而不会对我们的系统开发有任何实质作用。

 

用例表示的是使用系统的一个场景﹐其本质在于详细描述了系统用户(actor)与系统是如何交互的﹐以及交互的后果是什么﹐详细而完善的用例将指导您进行系统开发的全过程?

 

还以图书管理系统的借书为例﹐这是用例的文本描述(也是真正的用例)

 

系统﹕显示图书馆的所有书籍类别

用户﹕浏览并选择一个书籍类别

系统﹕显示这个书籍类别的所有书籍

用户﹕浏览并选择一本书籍

系统﹕显示书籍的详细信息

用户﹕借阅书籍

系统﹕办理预借手续

用例后果﹕产生一份预借记录。并将预借记录与书籍关联。

 

领域模型如何提取的具体办法就不讲了﹐其实在基本的面向对象的书籍中都有提到过﹐一个最简单的方法就是从语句中的名词找出领域对象。

 

经分析﹐这里的领域模型有﹕图书馆﹐书籍类别﹐书籍﹐预借记录(所有类别﹐所有书籍﹐详细信息这些也是名词﹐但是经过分析﹐它们分别是属于图书馆﹐书籍类别﹐书籍的属性﹐而不是领域模型)

 

 

领域模型中最重要的是领域模型之间的关系(当然还有属性)﹐我们的系统的逻辑也是体现在领域对象与对象之间的关系上(如您借了一本设计模式的书﹐在系统中就是设计模式这本书对象与借阅记录对象关联)

 

得到领域模型后﹐再根据用例﹐我们分析出领域模型对象的交互过程。

 

 

请注意﹐这里的显示****,并没有UI过程﹐实际上就是返回这个对象的某个属性﹐至于如何显示﹐那是UI层的故事。

 

根据借阅用例的描述﹐我们不难得到用例的业务过程的代码表示了﹕

借阅记录  aaa = new 借阅记录

设计模式.借阅记录 = aaa;

 

很简单﹐就是产生一个借阅记录对象﹐并将它与书籍对象关联﹗

 

依照迭代过程﹐我们对所有的用例都完成这样的建模过程。系统也就完成了。

在不断的迭代过程中﹐有很多属性呀﹐过程呀﹐都会逐步地由浅至深﹐最终完全符合了用户的要求。

 

在完成这些过程之后﹐一个系统的领域模型就建造完毕。所有系统的本质属性都体现在这些领域模型的活动过程之中。

 

一个系统要变更业务逻辑﹐我们只要针对领域模型作变化即可﹐再也不需要抱怨变化了。

 

终于要考虑系统其它部分了。

数据库﹐日志﹐邮件通知﹐当然还有UI(在之前的评论中有详细描述﹐感兴趣的朋友可以去找找这几天的评论)

这些都属于系统核心以外领域模型以外的东西。

 

这些模块与领域模型的接触在哪里呢?当然不能在领域模型内部﹐您不能在借阅逻辑中加入这些对象。

 

只有一个地方﹐那就是边界。

 

系统边界﹐即域控制器﹐即上面顺序图的系统对象所处的位置﹐系统对象除了与领域模型﹐用户打交道以外﹐它还会与系统的其它模块交互。如持久化系统(您不能因为客户由sql server要求转向oracle而去更改业务逻辑吧)﹐日志系统(您不希望修改日志规则而影响业务逻辑吧)﹐信息通知系统(您不能因为用户要求由邮件通知改为短信通知就修改领域模型吧)

 

将领域模型独立于持久化系统的真正原因﹕系统业务逻辑与如何存储数据以便使系统正常持续运行无任何关系。

 

当然﹐还有设计原则也会支持这种做法。持久化系统依赖领域模型时﹐只有领域模型本身变动才需要修改持久化过程﹐反之则不然﹐这正是依赖倒置原则的一个体现。

 

我稍微写一下域控制器的代码实现过程吧﹕

 

Public void 借阅()

{

        //这个借阅处理者是纯粹的软件对象﹐其存在的目的就是将借阅的实际处理过程独立出来

        借阅处理者 处理者 = new 借阅处理者(当前书籍﹐当前登录人姓名);

        Bool successful = 处理者.借阅()            //這個方法主要就是上面的那2行代碼

        If(successful){

                持久化系统.add(当前书籍);

                日志系统.add日志(当前当籍,”借阅”)

                邮件系统.发送邮件(当前书籍.当前借书人姓名)

        }

               

 

       

}

 

Void 持久化系统.add(书籍 当前书籍)

{

        借阅关系 Bbb = new 借阅关系

        Bbb.id = 产生ID()

        Bbb.图书ID = 当前书籍.id;

        Bbb.借阅人 = 当前书籍.借阅记录.借阅人姓名

        Bbb.借阅时间 = 当前书籍.借阅记录.借阅时间

        Bbb.Save();

}

 

其它模块处理过程类似。

 

從這里可以看出﹐業務過程是系統的核心﹐其它模塊都是依賴于它而存在。

 

如果有ORM﹐它使用的地方就是这里了。

 

在现实系统中﹐我在if(successful)这里作了一些纯软件设计﹐如利用C#具有Event特性﹐将借阅方法后公开出一个事件﹐这样我在再要添加什么外围模声时﹐只要响应事件就可以了﹐不需要再来动这个方法

 

另外一个不容回避的问题﹐统计报表﹐虽然循环内存中的对象﹐理论上完全可以抓出所有的数据。

但是对于这一类问题﹐我就不麻烦领域对象了(其实它与领域模型﹐业务逻辑也真的没有关系)﹐而是将它独立实现﹐直接调用持久化模块﹐下sql抓资料。

 

还有一个实际的问题﹐就是对象的加载。理论上﹐每次系统重启后﹐都要将所有内存中的对象全部还原﹐构建领域环境。

然而对于大部分实际运用的系统﹐这是不可能的﹐如这里的图书馆对象﹐它可能有上千本书﹐因此一次加载肯定不行﹐我使用的方法是﹐利用Proxy﹐继承领域模型﹐延迟加载。关于这一点﹐就属于纯设计问题了﹐如何实现﹐欢迎大家有兴趣再探讨﹗


(請教一下大家用什么方法發表blog呀﹐我每次從word中貼過來好痛苦)

posted on 2007-09-28 10:23 Kevin Zou 阅读(5461) 评论(28)  编辑 收藏 网摘 所属分类: asp.net

Feedback

很全面,
  回复  引用  查看    

#2楼 2007-09-28 10:45 daizhj      
虽然你画的领域模型有些问题还要考就,但主要的基本概念的确是交待清楚了,这么短时间写了这么多,不易呀:)
  回复  引用  查看    

很清楚.
  回复  引用    

#4楼 2007-09-28 10:55 怪怪      
呵呵, 我直接手打~
  回复  引用  查看    

#5楼 2007-09-28 10:57 怪怪      
这篇文章确实深入浅出, 对已经有很多经验但不是那么正规的程序员有很好的理清思路的作用 :)
  回复  引用  查看    

#6楼 2007-09-28 11:00 亚历山大同志      
按照《UML和模式应用》在第100页的描述
领域模型就是对领域内的概念类和现实世界中对象的可视化表示【Mo95,Fowler96】。

针对UP来说领域模型就是业务对象模型。

  回复  引用  查看    

#7楼 2007-09-28 11:00 徐少侠      
使用园子推荐的Windows Live Writer
或者看看园子是不是支持直接Word2007发布

  回复  引用  查看    

#8楼[楼主] 2007-09-28 11:16 小生      
@怪怪
亞同志的<UML和模式應用>才是真正的深入淺出﹕)

發此文主要還是將實際應用過程和碰到的問題再描述一次﹐希望大家在以后碰到這些問題時能夠一起討論.

@徐少侠
不是經常發文﹐所以沒有注意﹐有空試用一下...

  回复  引用  查看    

#9楼 2007-09-28 11:29 亚历山大同志      
常发帖,常被砸,砸啊砸得就开窍了
  回复  引用  查看    

#10楼 2007-09-28 12:27 东风31      
最近在看《需求过程(第2版)》,里面也谈到了领域这个概念。
我理解的领域是对业务工作进行归类划分,归类的方式是业务工作具有相关的知识,这些所需要的知识构成一个领域,这些知识是业务工作的背景,通过对领域的分析,可以帮助我们挖掘、分析、理解业务工作的本质。
也就是说,领域是为需求分析工作服务的,它的目的是挖掘、分析、理解业务工作的本质。

  回复  引用  查看    

#11楼 2007-09-28 14:42 Klesh Wong      
Void 持久化系统.add(书籍 当前书籍)
{
借阅关系 Bbb = new 借阅关系
Bbb.id = 产生ID()
Bbb.图书ID = 当前书籍.id;
Bbb.借阅人 = 当前书籍.借阅记录.借阅人姓名
Bbb.借阅时间 = 当前书籍.借阅记录.借阅时间
Bbb.Save();
}

借阅关系 是业务实体?还是?

  回复  引用  查看    

#12楼[楼主] 2007-09-28 15:13 小生      
@Klesh Wong
不是業務實體(領域模型)
只是為了方便﹐而隨手組織起來的一個類﹐就相當于以前討論的那些Book,BookManager

  回复  引用  查看    

#13楼 2007-09-28 15:26 小宇哥哥      
我感觉这个文章吧,把问题给复杂化了.
  回复  引用  查看    

#14楼 2007-09-28 15:31 Klesh Wong      
@小生
I see

  回复  引用  查看    

#15楼 2007-09-28 18:15 dudu      
好文章!
你用的是Word 2003吧,用Word 2007直接粘贴到编辑器,排版不会乱。
你试试将Word中的内容复制到Live Writer,然后发布试试。

  回复  引用  查看    

#16楼 2007-09-29 09:00 Clark Zheng      
直接用Windows Live Writer写

http://www.cnblogs.com/reonlyrun/archive/2007/07/11/814370.html" target="_new">http://www.cnblogs.com/reonlyrun/archive/2007/07/11/814370.html

  回复  引用  查看    

#17楼 2007-09-29 09:26 Baoquan[未注册用户]
@小生,你的文章给我很大的启发。
思想一碰撞,火花就来了:)

  回复  引用    

#18楼 2007-09-29 10:31 ttvnc[未注册用户]
好文章,做系统还是要业务分析为主
  回复  引用    

#19楼 2007-09-29 10:49 刘荣华      
大半月没上博客园。
今儿一来发现好文。不错。
LZ借助这个领域模型的概念来解释系统设计。
此文有必要继续再深入下去。
另外:提两句不成熟的意见:)
既然牵扯到持久化系统,那整个借阅的逻辑处理不应该把持久化处理.add()方法给独立出来。
如果系统只处理借书,还书这样的逻辑,这里UI层给用户调用的子系统,就应该只有借阅处理者,邮件处理者,日志处理者,三者又是相对独立的对象。
Public void 借阅()
{

//这个借阅处理者是纯粹的软件对象﹐其存在的目的就是将借阅的实际处理过程独立出来

借阅处理者 处理者 = new 借阅处理者(当前书籍﹐当前登录人姓名);
//××我认为持久化处理应该放在各子系统中调用
借阅处理结果 Result = 处理者.借阅()
If(Result == 借阅处理者.处理结果.成功){
日志系统.add日志(当前当籍,”借阅”)
邮件处理结果 = 邮件系统.发送邮件(当前书籍.当前借书人姓名)
if (邮件处理结果 == 邮件系统.处理结果.成功)
{
日志系统.add(邮件系统.错误,邮件系统.错误信息);
}
}else
{
................
}

}

  回复  引用  查看    

#20楼 2007-09-29 10:52 刘荣华      
。。。。。-_-!!错了
if (邮件处理结果 == 邮件系统.处理结果.成功)
{
日志系统.add(邮件系统.错误,邮件系统.错误信息);
}
--------------------------------------------
if (邮件处理结果 != 邮件系统.处理结果.成功)
{
日志系统.add(邮件系统.错误,邮件系统.错误信息);
}

  回复  引用  查看    

#21楼 2007-09-29 14:04 txdlf      
用例分析的不错~只是代码实现的太糙
期待继续完下去

  回复  引用  查看    

好像挺正规的,因为我开始看不懂了。呵呵。
  回复  引用  查看    

#23楼 2007-10-10 14:53 GerryJiang      
楼主这个
持久化系统.add
独立出来是什么原因呢?

  回复  引用  查看    

#24楼 2007-10-26 11:00 半山旅客
学习中。。。
  回复  引用    

#25楼 2008-11-03 23:16 破甲      
书籍和借阅记录是个聚合关系吧?
那这样合理吗:
借阅记录 aaa = new 借阅记录

设计模式.借阅记录 = aaa;

  回复  引用  查看    

#26楼 2008-11-03 23:29 破甲      
另外我想请教这么个问题:
在你的例子中借阅关系 是一个领域模型吗???(你只列举了书籍,图书馆,借阅记录,书籍类别)


我就是想请教 在数据库可以反映的出来的表关系 ,领域模型怎么处理 ,,不如那个“借阅关系”

  回复  引用  查看    

你并没有解释清楚领域、应用域的概念。
你的时序图大大的错。

  回复  引用    

#28楼 2009-03-17 10:24 MyXQ      
牛人,牛人,牛人,牛人,牛人。
  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 908983




相关文章:

相关链接: