随笔 - 60  文章 - 24 评论 - 1560 trackbacks - 29

.net asp web c# vb VS2005 VS2008 VS2003

    姓名 景春雷
    网名 1-2-3
    生日 1980.2.29
    城市 沈阳
[生活]昨晚看了变形金刚2。场面火爆,观众也火爆,居然买不到7:30的票,只好等9:30那场。PS:高中以后就没去过电影院了。

与我联系

搜索

 

常用链接

我参与的团队

我的标签

随笔分类(59)

随笔档案(57)

文章分类(23)

相册

收藏夹(2)

积分与排名

  • 积分 - 207440
  • 排名 - 223

最新评论

阅读排行榜

评论排行榜

      “请问我从这儿出发应该走哪条路呢?”
      “这多半看你要去哪儿。”猫说。
      “我不太介意去哪儿——”爱丽斯答道。
      “那你走哪条路都无所谓。”猫说。
      “——只要我最后能到一个地方就可以了。”爱丽斯补充说。
      “哦,当然,”猫说,“只要你走得够远,你一定可以做到的。”
                                         ——《爱丽斯漫游奇境》

摘要

本文通过由Active Record模式到Data Mapper模式(使用工厂方法)再到Data Mapper模式(使用MapperRegistry)的一系列重构,探讨模式背后隐藏的思想和面向对象设计原则。本系列的要点是:重要的不是如何做,而是为什么做。

适用读者

基本上,我们猜测本系列的读者是
A. OO达人,但是没看过Martin Fowler的《企业应用架构模式》一书。本系列所探讨的Data Mapper和Ghost等模式都来自《企业应用架构模式》,不过即使您没看过这本书也没关系,我们会对文中涉及到的模式作简要介绍,相信凭借您丰富的OO经验和超强的理解能力,一定能轻松领悟这几个模式。

注:OO,指 Object Oriented,“面向对象”的缩写。

B. 刚刚看完《企业应用架构模式》,但是对书中的Domain Object和Ghost等模式相当迷惑。太好了!本系列就是为您而写的。愿笔者对模式的思考能给您带来一点帮助和启发。
C. SQL达人,对OO从来不感冒。相信您看了本文一定会说:"切,这么简单的问题用两个SQL语句不就搞定了么?干嘛非要用花哨的OO?"嗯,既然您已经是可以用SQL作任何事情的达人,工作对您还有什么挑战性呢?不如开始尝试一下OO,换换口味吧^_^
D. 刚刚修完C语言的大二学生,OO这东西,听说过没见过。也许您会像中途入场的电影观众那样,有一种前不着村后不着店的感觉。不过没关系,反正您早晚要学这些东东的嘛,不如先看看本文,找找感觉。特别是后面列出来的参考文献,都是最近几年最流行的OO经典,不容错过呦。
E. 娱乐记者。您可能正在Google上搜索"某某明星家中闹鬼事件",一不小心进来了。没关系,即来之则安之。相信本文那些生硬古怪的比喻、弯来绕去的图形一定会让您大呼过瘾。您一定会惊诧于这个世界上居然还有人会为了"分层设计"这种无聊的事情苦思冥想,好像如果让下层访问了上层,世界就会颠倒了似的。
X.  即是OO达人又精通企业应用架构。这篇粗浅的文章可能对您帮助不大,但恳请您在离开之前能指点一二,在下先行谢过了!

Active Record 模式

这是最简单的一种对象模型了。书上是这么描述它的:

An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.
一个封装了数据库表或视图中的一条数据的对象,它不但负责对数据库的访问,而且含有业务逻辑。

很明显地,Person类有两个职责:
    - 访问数据库,形成对象与数据库之间的映射。
    - 封装数据库中的一条数据,并含有业务逻辑。

这违反了“单一职责”这一设计原则。

Single Responsibility Principle
A class should have only one reason to change.
单一职责原则
每个类应该只有一个需要进行修改的理由。

我们的Person类有两个可能的修改理由——业务逻辑发生变化时和创建/持久化对象的方法、策略发生变化(例如想在优化性能时采用LazyLoad)时——这是我们不愿看到的。我们需要进行一次重构,将访问数据库的职责从Person中分离出去。

重构,将访问数据库的职责从Person中分离出去

我们进行一次Extract Class重构,将find()、insert()、update()和delete()这四个函数从Person类转移到PersonMapper类中。


Client 代码会像这个样子:
    Person p1 = Person.find(101);
    p1.salary = p1.salary * 1.20;
    p1.update();

为我们的程序添加两个类

我们的数据库中还有一个DEPARTMENT表,它与PERSON表是一对多的关系。


表中的数据如下:

 PERSON表  

ID

FIRST_NAME

LAST_NAME

SALARY

DEPARTMENT_ID

101

Neena

Kochhar

17000

20

102

Lex

De Haan

17000

20

103

Alexander

Hunold

9000

20

105

David

Austin

4800

40

106

Valli

Pataballa

4800

40

107

Diana

Lorentz

4200

50


 DEPARTMENT表  

ID

DEPARTMENT_NAME

LOCATION

MANAGER_ID

20

Marketing

LN Shenyang

102

40

Human Resources

Beijing

105

50

Shipping

Shanghai

107


让我们在程序中添加一个Department类和一个DepartmentMapper类。


每当我们看到像Person和Department这种相似的类,就会情不自禁地想(强迫症?)是否存在着重复的代码可以被提炼出来。
我们需要为Person和Department添加一个抽象的父类,因为
    - 这样可以为Client代码提供一个抽象的访问接口,符合针对接口编程的设计原则。
    - 减少重复代码,以后修改代码将会更容易。
    - 子类得到了简化,新增子类将更容易。
    - 可读性更好(当然是对OO达人而言,OO菜鸟会变得更迷惑)。
    - 要是不弄个抽象、接口什么的,怎能显得我们的设计有够OO?怎能把新来的菜鸟唬得一愣一愣的?

Design Principle
Program to an interface, not an implementation.
设计原则
针对接口编程,而不要针对实现编程。

提炼超类,重构成工厂方法模式

让我们作一个Extract Superclass重构,提炼出Person和Department类的超类DomainObject 以及 PersonMapper和 DepartmentMapper类的超类AbstractMapper。它们都是抽象类。



第一件工作是创建一个抽象类DomainObject,让Person和Deparment继承它。然后将insert()、update()、delete()和find()函数提升到DomainObject中。
不过这里有一个问题。Person#update() 里写的是“new PersonMapper().update(this);”,而Department#update() 里写的是“new DepartmentMapper().update(this);”。如果把update()函数提升到DomainObject类中,该如何创建合适的Mapper对象呢?
解决方法是让“创建合适的Mapper”的工作仍然由子类负责,DomainObject负责其余的工作。为此,我们需要在DomainObject中创建一个抽象函数createMapper(),DomainObject#find()等函数调用这个抽象函数完成工作。
我们还添加了一个抽象类AbstractMapper作为访问各个具体的Mapper类对象的接口。

现在Client代码就可以针对接口编程了:
    IList<DomainObject> dirtyObjects = new List<DomainObject>();
    dirtyObjects.Add(person1);  // person1 is an instance of Person
    dirtyObjects.Add(person2);  // person2 is an instance of Person
    dirtyObjects.Add(department1);  // department1 is an instance of Department
    // update all dirty objects.
    foreach(DomainObject dirty_object in dirtyObjects)
    {
        dirty_object.update();
    }

现在,我们的类结构已经是一个标准的工厂方法模式了。“工厂方法”就是createMapper()函数,产品是AbstractMapper类层次。

The Factory Method Pattern
Difines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
工厂方法模式
定义一个用于创建对象的接口,让子类决定创建哪一个类的实例。工厂方法模式让创建对象的工作延迟到子类去进行。

为什么说Domain Object不应该知道Mapper?

现在,我们的设计已经使用了经典的工厂方法模式,还存在什么问题么?为什么Matin Folwer在书中多次强调Domain Object不应该知道Mapper?这是因为在大型的信息系统中,业务逻辑可能会很多很复杂。相应地,Domain Object类层次的结构也会很复杂。Domain Object操心自己的事儿就已经很累了,我们不希望它还要操心自己的持久化问题——一心不可二用嘛。换句话说,我们希望将Domain Object放到一个单独的中,这是应用了分层设计的思想。

注:在本系列中,所谓“类A知道类B”中的“知道”与“调用、使用、引用、访问、创建、依赖”都是一个意思。

分层设计

分层
从不同的层次来观察系统,处理不同层次问题的对象被封装到不同的层中。



进行分层设计时,要注意以下几点:
    - 层和层之间的耦合应该尽可能地松散。也就是说上层应该尽量通过下层提供的接口使用下层提供的功能和服务。当然只是“尽量”,并不是绝对不能访问具象类。
    - 每一层应当只调用下一层提供的功能服务,而不能跨层调用。这一条也不是绝对的。可以根据需要灵活处理。
    - 每一层决不能使用上一层提供的功能服务,也就是说,决不能在层与层之间造成双向依赖或循环引用。这一条是必须遵守的。如果违反了这一条,分层设计就没有意义了。

这里的“层”是逻辑概念。不过如果你喜欢,可以使用物理方法来强化“层”的感觉,例如可以将不同的层放入不同的类库中并使用不同的命名空间。



书上的Data Mapper模式

Matin Folwer给出的Data Mapper模式就是使用了分层设计思想。Domain Object是绝对不可以知道Mapper的。

A layer of Mappers (473) that moves data between objects and a database while keeping them independent of each other and the mapper itself.
Mappers层负责在objects和database之间移动数据。它可使objects 和 database互不依赖,并且objects 和 database也不依赖于Mapers。


Person不依赖于Person Mapper,Persson Mapper 依赖于Person,这是由Mapper层到domain object层的单向依赖,符合分层思想。Mapper层是domain object层的上层。Default.aspx.cs依赖于Person Mapper和Person,所以表现层位于Mapper层和domain object层之上。


这个图可能与直觉不符。我们通常总是在说“表现层、业务逻辑层、持久化层”,好像与数据库相关的东西就应该在最下面才对。如果您觉得不能相信自己的眼睛,就请再仔细地想一下这个问题,因为分层思想即是本篇的重点又是下篇的基础,一定要想清楚才行。

将Factory Method版的DataMapper重构成分层模式的DataMapper

也就是说,要让DomainObject类层次不依赖于Mapper类层次。方法是删除DomainObject中依赖于Mapper的insert()、update()、delete()、find()和createMapper()函数。


很好,现在DoaminObject类层次不依赖于Mapper了,可是“创建合适的Mapper”的工作要交给谁来作呢?交给Client么?如果交给Client来作的话,会像这样:
IList<DomainObject> dirtyObjects = new List<DomainObject>();
dirtyObjects.Add(person1); // person1 is an instance of Person
dirtyObjects.Add(person2); // person2 is an instance of Person
dirtyObjects.Add(department1); // department1 is an instance of Department
// update all dirty objects.
foreach(DomainObject dirty_object in dirtyObjects)
{
    if(dirty_object is Person)
    {
        new PersonMapper().update(dirty_object);
    }
    else if(dirty_object is Department)
    {
        new DepartmentMapper().update(dirty_object);
    }
    else
    {
        thow new Exception("should never reach here!");
    }
}

这又违反了“要针对接口编程,而不要针对实现编程”这一设计原则(涂黄颜色的部分即是典型的针对实现编程)。想一想,当我们需要添加一个新的domain object和mapper的时候,会有数不清的Client代码需要修改。要是遗漏了一处,也只有在运行到那段Client代码的时候才会报错,那真是噩梦啦。
解决方法是,将“创建合适的Mapper”的工作要交给一个单独的具有全局唯一访问点的单件类MapperRegistry类来作。

重构,将创建Mapper实例的代码移动到MapperRegistry中

我们需要作一个Extract Class重构,将创建Mapper实例的代码移动到MapperRegistry中。



Web程序中的MapperRegistry的Example:
public class MapperRegistry 
{
  
private IDictionary<Type, AbstractMapper> mappers = new Dictionary<Type, AbstractMapper>();
  
public static MapperRegistry instance
  {
    
get
    {
       MapperRegistry result 
= System.Web.HttpContext.Current.Session["MapperRegistrySingleton"as MapperRegistry;

       
if (result == null)
       {
         System.Web.HttpContext.Current.Session[
"MapperRegistrySingleton"= new MapperRegister();
         result 
= System.Web.HttpContext.Current.Session["MapperRegistrySingleton"as MapperRegistry;
       }

       
return result;
    }
  }
  
private MapperRegistry()
  {
    mappers.Add(
typeof(Person), new PersonMapper());
    mappers.Add(
typeof(Department), new DepartmentMapper());
  }
  
public AbstractMapper getMapper(Type t) 
  {
    
return mappers[t];
  }
  
public PersonMapper person
  {
    
get { return getMapper(typeof(Person)) as PersonMapper; }
  }
  
public DepartmentMapper department
  {
    
get { return getMapper(typeof(Department)) as DepartmentMapper; }
  }
// class MapperRegistry

Client代码会变成这样:
IList<DomainObject> dirtyObjects = new List<DomainObject>();
dirtyObjects.Add(person1); // person1 is an instance of Person
dirtyObjects.Add(person2); // person2 is an instance of Person
dirtyObjects.Add(department1); // department1 is an instance of Department
// update all dirty objects.
foreach(DomainObject dirty_object in dirtyObjects)
{
  MapperRegistry.instance.getMapper(typeof(dirty_object)).update(dirty_object);
}

工厂方法 VS Registry

现在已经很清楚了,Registry和工厂方法实在是有着异曲同工之妙。我个人觉得工厂方法模式更自然一些,不过为了分层设计的需要,不得不用Registry代替工厂方法。使用Registry,在添加新的domain object和mapper的时候,有时会忘记在MapperRegistry中Add它们。
天杀的,我是每次都会忘记啦!
在第100次看到Exception的时候,我终于明白,靠人的自觉性不犯错误,简直就是天真的理想。

思考题

1. 分层设计不但被软件设计广泛使用,在计算机的其它领域(硬件、网络等)也有广泛的应用,请举出一些例子。
2. 不但在计算机领域,在其它非计算机领域也广泛使用了分层的思想,请举出几例。

参考文献

[Fowler POEAA]
Fowler, Patterns of Enterprise Application Architecture. Addison-Wesley, 2003.
影印版:企业应用架构模式(影印版)。中国电力出版社,2004。
[Fowler Refactoring]
Fowler, Refactoring: Improving the Design of Existing Code. Addison-Wesley, 1999.
影印版:重构——改善既有代码的设计(影印版)。中国电力出版社,2003。
[Fowler UML]
Fowler et al, UML Distilled: A Brief Guide to the Standard Object Modeling Language(Sencond). Addison-Wesley, 2000.
中文版:徐家福 译,UML 精粹(第2版)标准对象建模语言简明指南。清华大学出版社,2002。
[Freeman et al]
Freeman et al, Head First Design Patterns. O’Reilly, 2004.
影印版:深入浅出设计模式(英文影印版)。东南大学出版社,2005。
[王咏武 王咏刚]
王咏武 王咏刚, 道法自然:面向对象实践指南。电子工业出版社,2004。

工具箱

那个太极小图标来自《Head First Design Patterns》,用FireWorks 6.0和GIMP 2.2作了一些处理。UML图使用Visio 2003+Pavel Hruby's UML2.0 模板绘制。图片上使用了手写字体方正静蕾简体。文字部分使用Google 拼音输入法键入。

 
posted on 2007-10-15 08:54 1-2-3 阅读(3862) 评论(78)  编辑 收藏 网摘 所属分类: 探索Domain Model系列

FeedBack:
#1楼 2007-10-15 08:56 jillzhang      
老兄这么长的文章需要写1天吧?不过非常好,有点论文的味道
  回复  引用  查看    
#2楼[楼主] 2007-10-15 08:59 1-2-3      
@jillzhang
呵呵,我写了一个星期。一开始的时候UML图是用Dia画的。后来发现Pavel Hruby's UML2.0 模板更好用,又把图全部重画了一遍,眼睛都绿了。

  回复  引用  查看    
#3楼[楼主] 2007-10-15 09:34 1-2-3      
自己答个第2题
> 2. 不但在计算机领域,在其它非计算机领域也广泛使用了分层的思想,请举出几例。
在贩毒领域也应用了分层思想。
例如在电影《门徒》里,香港制毒、贩毒集团的幕后大Boss刘德华就一手架构了一个分层结构的制贩毒组织。这个组织分为
- 毒品制造层,黑话叫“厂”。
- 毒品保存层,黑话叫“库”。
- 毒品贩卖层,黑话叫“车”。
这些层没有上下层之分,因为它们彼此都不知道对方。那么这些层之间如何协作呢?它们之间使用一个窄接口进行通信,这个窄接口就是刘德华的关门弟子吴彦祖,黑话叫作“脚”。刘德华特意强调“要用脚把各个层隔开”。哇塞,刘德华要是改行作软件,俺们都得失业了。
您可能会问了,“直觉上,各层都依赖一个接口不是很危险么?”要注意各层依赖的是“窄接口”而不是“宽接口”,这个问题我会在下篇重点讨论。而且吴彦祖不但是“窄接口”,而且是一个“比较不易发生变化的接口”,因为他看上去即精明又坚强,讲义气又忠诚,深得刘德华信任。当然了,刘德华怎么也没想到吴彦祖其实是个卧底!接口没定义好,那刘德华还不死?

  回复  引用  查看    
#4楼 2007-10-15 09:39 涤生      
文章写的很飘逸,期待下文。
  回复  引用  查看    
#5楼[楼主] 2007-10-15 09:51 1-2-3      
@涤生
谢谢,下篇对我来说难度有些大,可能需要几个星期的时间。

  回复  引用  查看    
#6楼 2007-10-15 09:56 Anders Cui      
喜欢这样的文章
图文并茂,引经据典
看得出作者花了很大的心思
赞一个!

有个建议,最后的Registry是不是换成Repository更好
Factory和Repository的区别也更容易理解吧

  回复  引用  查看    
#7楼 2007-10-15 10:01 紫色阴影      
写的很好
推荐可以看看Eric Evans 的 Domain Driven Design

  回复  引用  查看    
#8楼[楼主] 2007-10-15 10:06 1-2-3      
@Anders Cui
谢谢。Repository我不了解,能否给点阅读建议?
另,Registry是《企业应用架构模式》中给出的一个模式,我一直在想它和工厂方法有何不同,最后写了这篇。

  回复  引用  查看    
#9楼 2007-10-15 10:08 怪怪      
好文.., 说的实在清楚, 期待后续 :)

另外你的Client是指aspx.cs么? 如果把aspx.cs更多的看做是View, 或者你Client代码部分的逻辑还可以重用, 按照前段时间小生说的, 可以把这些代码放进单独的Controller里去~~

感觉你这篇文章配合上小生的领域建模的文章, 把前段时间的争论给出了一个比较完美的答案.

  回复  引用  查看    
#10楼[楼主] 2007-10-15 10:10 1-2-3      
@紫色阴影
谢谢。以前听说《Domain Driven Design》是本好书,不知怎的后来就忘记了。谢谢您的推荐,我会把它加进读书计划之中。
PS:俺最近一直在看《算法导论》,8个月才看了100多页,耽误了太多时间了,很多好书都没来得及看 :(

  回复  引用  查看    
#11楼 2007-10-15 10:12 怪怪      
另外如果dudu看到这篇文章, 可以把这篇文章加入你那个面向对象集合贴推荐 :)
  回复  引用  查看    
#12楼[楼主] 2007-10-15 10:14 1-2-3      
@怪怪
谢谢,看了您的很多文章,觉得您基本上是批评多于肯定的。所以能得到您的肯定真是十分高兴啊^_^。

其实,那段用了List<DomainObject> 的Client代码应该是UnitOfWork代码的一部分。为了简明性,我没有提到UnitOfWork,但其实是应该使用UnitOfWork的。UnitOfWork是实现事务的一个模式。

  回复  引用  查看    
#13楼[楼主] 2007-10-15 10:16 1-2-3      
@怪怪
“你那个面向对象集合贴推荐”是什么?我好像没有这个东东呀?

  回复  引用  查看    
#14楼 2007-10-15 10:29 xiao_p      
接口没定义好,那刘德华还不死?,o(∩_∩)o...哈哈,这个评论笑死我了。。。

文章写的不错,可以说是深入浅出了。而且我发现,那个太极的图案就是深入浅出系列里面的,hoho!

  回复  引用  查看    
#15楼 2007-10-15 10:30 xiao_p      
@1-2-3
你在 blog首页推荐阅读里面找 ,就能看见怪怪说的那个链接了

  回复  引用  查看    
#16楼 2007-10-15 10:36 怪怪      
就是dudu为了统一话题, 自己开的一个把关于面向对象争论的话题都给放里头了~~ 等他整理完了, 园子也基本平静了, 我也会集合一下, 做个推荐, 你这篇是肯定要有的 :)

"这个图可能与直觉不符。我们通常总是在说“表现层、业务逻辑层、持久化层”,好像与数据库相关的东西就应该在最下面才对。"

这个就是所谓的Microsoft DNA以及给出的示例把大家给教坏了, 你看的那本POEAA里Martin提到过两句~~ 不是说MS DNA没有道理, 或者数据层没有办法放到最底下, 但是讲解他们提出的结构时的表达, 对MS这样的权威组织来说, 有欠考虑.

你说的窄接口的想法也非常不错, 我在这些争论里提出的一个思路也是点接触而不是面接触, 不过我把恢复对象状态的工作(即对属性的赋值工作)留给了对象自身内部. 且我的窄接口包含所谓的隐式接口~~

不知道你的窄接口如何做, 已经很期待下一篇文章了. 你说我批评多不好意思的说好像确实是这样, 其实我对我提意见的博主和他们想表达的东西并无成见, 关键是大家都没你这么认真一点一滴的写, 自然会出一些纰漏, 就让我给抓着了, 并非说我觉得自己很强~ 另外你可能觉得我不太把Fowler当回事吧? 看你引用了那么多Fowler的书 :) 其实我解释过了, 管Martin大嘴叫"老菜鸟", 其实是一种昵称, 充分肯定了他比我强的多, 只是感觉Martin象GoF那样创造性和扎实的贡献太少, 更多的象一个知道的很多的高级老师, 所以又有所区别罢了 :)

不过我的想法是, 对于一个普通复杂度的项目, 如果一个设计要是把Fowler书里的都实现了, 这个设计就太重型了, 所以不是说一定非得使用UnitOfWork或者其它的什么, 代码写多了, 自然就会有一些适合未来重构用得着的好习惯了, 这点上我倾向于半XP式的方式, 一点一点来~ 但是Martin说的这些模式(而非GoF的设计模式)有一个问题, 往往你实现了其中一个, 就发现最好把另外一个也实现了才通顺, 怎么找好折中点, 就成了一个问题, 也期待你说说你的看法 :)

  回复  引用  查看    
#17楼[楼主] 2007-10-15 10:41 1-2-3      
@怪怪
> 往往你实现了其中一个, 就发现最好把另外一个也实现了才通顺。
这句话深得我心。我就经常会这样的。

  回复  引用  查看    
#18楼[楼主] 2007-10-15 10:43 1-2-3      
@xiao_p
没错,就是从《深入浅出设计模式》的电子书里面抓出来的。

  回复  引用  查看    
#19楼 2007-10-15 10:58 怪怪      
@1-2-3
还有一句话我经常说, 是Java世界的某某讲的, "计算机科学就是通过增加一个层解决问题的科学", 原话记不住了, 这是我自己编的 :) ,看你的文章, 估计你也喜欢 :P

  回复  引用  查看    
#20楼[楼主] 2007-10-15 11:09 1-2-3      
@怪怪
呵呵,好像“增加一个层”是万能药^_^
不过增加一个层的缺点是系统变复杂了。记得三层架构流行以来,就不断的有质疑说“我在数据库的表中增加一个字段,就必须要改domian object 和mapper,然后改页面上的GridView和TextBox,累死了。”
记得Matin Folwer是这么回答的:“这就是所谓的trade-off——你得到了灵活性,自然要付出些代价。” 我觉得挺经典的。

  回复  引用  查看    
#21楼 2007-10-15 11:14 Anders Cui      
@1-2-3
Repository模式在POEAA中也有

  回复  引用  查看    
#22楼[楼主] 2007-10-15 11:19 1-2-3      
@Anders Cui
谢谢,看来我真是笨笨了,再仔细看过。

  回复  引用  查看    
#23楼 2007-10-15 11:59 怪怪      
@1-2-3
一些代价是可以消除的. 所以我觉得, 做trade-off时, 选对方式很重要, 有时候代价比你获得的东西还多, 也要仔细辨别..

对了, 你在下篇不会又把数据层的操作通过Martin所谓的Ghost等方式, 放回对象之下吧..., 这种做法不是不行, 不过我的想法是不要做Lazy Load, Lazy Load在数据库来回的开销, 比少占点内存要可怕的多...

  回复  引用  查看    
#24楼 2007-10-15 12:00 dudu      
麻烦作者调整一下摘要内容的排版。
  回复  引用  查看    
#25楼[楼主] 2007-10-15 12:06 1-2-3      
@怪怪
您可真厉害,连我要写什么都猜出来了。
我是想写点关于Lazy Load方面的东东的。不过我不会讨论Lazy Load的适用范围,而是有关如何设计Lazy Load方面的东东。

  回复  引用  查看    
#26楼[楼主] 2007-10-15 12:08 1-2-3      
@dudu
对不起,我是想在摘要里放个小图片吸引下眼球的,没想到被过滤掉了,结果变得很难看。我这就改过来。

  回复  引用  查看    
#27楼 2007-10-15 12:13 RicCC      
在分层结构里,将Mapper提到了Domain层之上,这个是有待考虑的

文章只假设了最简单的CRUD操作,从这些操作来看Domain不需要使用Mapper,但领域逻辑不会这么简单,某个对象方法为了完成特定的领域逻辑,经常需要与其它领域对象协作,这里面其它对象的加载、更新等一系列问题怎么处理?

仅仅为了Mapper与对象的映射问题,而把一个基础/底部结构层提到Domain层之上,得不偿失,严重一点是本末倒置了

Domain不能依赖于Mapper,通常都是在强调Mapper必须有统一的接口,实现上并不一定彻底解耦。从文章的设计思路来看,让Domain Object使用MapperRegistry加载Mapper也就轻松实现解耦了。另外例如使用NHibernate或者其它的OR框架,可以设计一个全局的Adapter,或者每个Domain Object/Object Hierarchy一个Adapter,这样即使将OR框架换成其它的也只是Adapter的事情。不同的做法Domain Object跟Adapter之间会有不同的耦合程度,但这没有什么影响了。

在Domain与View/Controller之间可能会有其它层,例如Service层,但不是Mapper

  回复  引用  查看    
#28楼 2007-10-15 12:14 RicCC      
any way,很好的文章,继续努力
  回复  引用  查看    
#29楼 2007-10-15 12:17 怪怪      
@1-2-3
倒 :).

看来你也认为按照先后顺序, 你上面画的图数据源在业务层之上是不合理的了... 其实你那个只是顺序图, 只是说数据操作先于逻辑操作. 按照前段时间小生说的, 领域模型最理想的状态是, 一开机就全部恢复进内存, 但是这根本无法实现, 不论实现的问题, 假设它能实现且数据还是在数据库中持久, 开机以后先打交道的是谁? 我个人认为, 必然是某Controller先从数据库中使用Mapper恢复全部数据...

我的想法是, 对于某过程而言, 一个请求发生, 最理想的状态是该过程需要的数据是可预测的, 就当开了一次机, 恢复这次开机所需要数据, 其它数据只当不存在(因为对业务中这次的过程是没用的).. 但这样的方式如果按照你的方式操作和理解, 数据源要先于领域模型存在. 但是到底谁在上谁在下, 并非是由那种操作先进行而划分的.., 这就是我说的三层结构图的误导人之处..

我这只是建议啊, 不是否定, 也不是批评 :P

  回复  引用  查看    
#30楼 2007-10-15 12:28 怪怪      
@RicCC
我个人觉得, 只是LZ错把顺序当作了三层结构的分层, 是个表述问题. "本末倒置"与否, 还要看具体应用, 而不是看先调的领域模型, 还是先从数据库恢复. 确实复杂些的应用, 存在你说的"需要与其它领域对象协作", 但其它领域对象还不在内存中, 需要加载, 更新等问题. 但是光从顺序先后来定上下, 那么在领域模型之上的"基础/底部结构层", 就多了去了, 也不多恢复数据的操作一个~~

当然, 碰到BL本身产生的装载问题, 确实数据也不可能在领域对象之前. 我的想法是, 应该象CPU一样, 写一些分支预测器, 来一次读取若干数据, 但是这是领域模型与外部系统打交道的事情, 确实应该单独放入领域模型之外, 然后在领域对象上做触发器, 或Ghost等模式.

但无论如何, 你所说的Domain Object与其它对象的耦合就产生了. 这个LZ没有讲, 估计下篇文章就会说到了 :) LZ不是说通过窄接口来做这件事么..

  回复  引用  查看    
#31楼[楼主] 2007-10-15 13:28 1-2-3      
@RicCC
看了您的Blog,发现您写了多篇NHibernate的文章。说来脸红,俺还没有用过NHibernate。刚刚收藏了您的文章,以后慢慢看。不知到NHibernate等主流框架是如何架构的,不知您能否给讲讲。

  回复  引用  查看    
#32楼 2007-10-15 13:30 gakaki[未注册用户]
javaeye有篇著名的帖子是讲 贫血的还是充血的领域模型的
  回复  引用    
#33楼[楼主] 2007-10-15 13:36 1-2-3      
@gakaki
地址呢?急死我......

  回复  引用  查看    
#34楼 2007-10-15 13:51 怪怪      
@gakaki
别提那个帖子了, 毫无意思, 恐怕连Martin的书好好读过的人都没几个...

  回复  引用  查看    
#35楼[楼主] 2007-10-15 13:55 1-2-3      
--引用--------------------------------------------------
怪怪: @gakaki
别提那个帖子了, 毫无意思, 恐怕连Martin的书好好读过的人都没几个...
--------------------------------------------------------
怎么会?感觉搞Java的应该更热衷于Martin才对。(偷偷地小声说,俺搞过1个月Java)。

  回复  引用  查看    
#36楼 2007-10-15 14:15 怪怪      
@1-2-3
他们是更热衷于证明自己谁更懂OO.

就那个帖子里说来说去的, 什么词确实都冒出来了, 巨唬人, 就我看比Martin的书里的各种词还得多, 但是仔细观察每个作者, 都是只掌握其中一部分, 没人愿意去理解和体会其它人说的那一部分, 这样大多数人都不是在一个完整的认知的情况下, 你说你的我说我的, 对书上东西的理解也仅考虑自己的实践经验, 那可不都觉得自己有理, 我怀疑都是就着自己愿意看容易懂的部分看的..., 毕竟人家Matin把领域模型什么的和面临的情况都说得很清楚了, 全都有使用时机的问题, 如果好好看过, 怎么还会在这些问题上拿着Martin当武器瞎吵吵呢.

Javaeye上不乏一些有价值的想法, 即使是一些学究化的文章. 但是就面向对象啦, 贫血啦这些东西的文章, 只要吵起来, 必定是一堆菜鸟瞎吵吵. 毕竟越难说清楚的问题, 趟混水就越不容易丢丑. 真正像样的文章有时是不太容易看太明白, 往往又是老人写的, 大家都怕说错了丢人, 也就没人敢乱说乱炸刺儿.

其实国内技术圈很多讨论都是这样, 不是看你说的对不对, 而是看你是不是大家公认的牛人; 不是找你文章里有价值的部分, 而是就这自己的经验和理念, 一味强调自己的理解, 认为你不懂; 如果你文章再犯几个错, 除非你脸皮够厚, 否则面子就别要了.

对这种风气很反感...., 我在现实生活里也见过这样的, 但是人家是为了饭碗, 如果出现了这种人, 我宁可不要项目也不砸人饭碗, 不知道网上干嘛也搞这么热闹...

  回复  引用  查看    
#37楼[楼主] 2007-10-15 14:25 1-2-3      
@怪怪
> ...都是只掌握其中一部分, 没人愿意去理解和体会其它人说的那一部分.
其实俺也是这样的,自省中......

> 毕竟越难说清楚的问题, 趟混水就越不容易丢丑. 真正像样的文章有时是不太容易看太明白
我是宁可写错被人贬,也不要写得不清不楚的。这是我对自己的文章的一个起码的要求。我宁愿读者说1-2-3这个地方写错了,那个地方是胡说八道,但不会说看了半天,不知道你写得是对是错^_^

  回复  引用  查看    
#38楼 2007-10-15 14:49 无处坏      
好文章,小弟学了,谢谢好心的楼主
  回复  引用  查看    
#39楼 2007-10-15 15:17 无处坏      
楼主能不能把下一篇提前点时间发出啊,小弟也正想看一下下一篇,看了收益很多,谢谢

  回复  引用  查看    
#40楼 2007-10-15 15:29 RicCC      
--引用--------------------------------------------------
怪怪: @RicCC
我个人觉得, 只是LZ错把顺序当作了三层结构的分层, 是个表述问题...
但是光从顺序先后来定上下, 那么在领域模型之上的"基础/底部结构层", 就多了去了, 也不多恢复数据的操作一个~~...
当然, 碰到BL本身产生的装载问题, 确实数据也不可能在领域对象之前. 我的想法是, 应该象CPU一样, 写一些分支预测器...
--------------------------------------------------------
client发出一个请求,一般来讲server很可能第一步需要加载一些领域对象,直接使用data map来加载不太好,最好使用领域对象的类方法(例如一些静态方法)加载对象实例,然后使用实例方法完成业务逻辑。至于加载过程,可能就是使用文章中的这种data map方式。

data map为什么最好是置于domain object之下?精细设计的情况下,db schema与object schema可能差别会很大,对象体系结构中可能通过聚合封装掉了很多细节,让domain之上的对象看到这些细节不是件好事情

这样一来data map层与domain层的职责就相当明显了

  回复  引用  查看    
#41楼 2007-10-15 15:31 RicCC      
@怪怪
预加载是一种不错的想法,不过如何做到按需加载,又跟复杂的领域模型解耦,足以难倒一片人

  回复  引用  查看    
#42楼 2007-10-15 15:49 RicCC      
--引用--------------------------------------------------
1-2-3: @RicCC
看了您的Blog,发现您写了多篇NHibernate的文章。说来脸红,俺还没有用过NHibernate。刚刚收藏了您的文章,以后慢慢看。不知到NHibernate等主流框架是如何架构的,不知您能否给讲讲。
--------------------------------------------------------
NHibernate整体上看比较简单,找一些简单例子看一看就能了解
具体用法细节方面比较复杂,因为基本上各种领域模式在里面都有体现和使用上的best practice

  回复  引用  查看    
#43楼[楼主] 2007-10-15 16:43 1-2-3      
@无处坏
谢谢您喜欢这篇文章。我下篇想写关于lazy load 的东东,但是俺觉得对Lazy Load的理解还不够,想在从头把Lazy Load看一遍再写。估计最少要两周时间。加上又有临时出差任务,我想怎么也得下个月了,抱歉,抱歉呵。

  回复  引用  查看    
#44楼 2007-10-15 17:01 Anders06      
赞!图文并茂,太爽心悦目了!!!

POEAA买了老久,却没翻过几章,很多概念难以建立啊,非常喜欢LZ的文章,期待下文

  回复  引用  查看    
#45楼 2007-10-15 17:23 lovecherry      
好文,居然花一个星期写一篇
我写随笔一篇不超过2小时。。。

  回复  引用  查看    
#46楼 2007-10-15 23:06 dudu      
看这样的文章是一种享受!谢谢1-2-3!
这种写文章的态度值得我们学习!
文章已经收录到http://www.cnblogs.com/dudu/archive/2007/09/19/898911.html">关于面向对象的讨论。
摘要中已经允许显示图片。

  回复  引用  查看    
#47楼 2007-10-16 01:39 怪怪      
@RicCC
所以我说LZ应该放入某一家在逻辑的对象封装起来, 静态方法也一样, 如果不考虑变更的话 :)

@dudu
改程序了? :P

  回复  引用  查看    
#48楼 2007-10-16 02:04 saucer[未注册用户]
写得蛮好的!

一个问题是,假如Person或Department或其他的类有关联,那么究竟该在哪个类里做持久操作呢?

接下来,你会发现,其实这些Mapper类的实现差不多,有必要减小重复性,

什么

dirty_object.update();

new DepartmentMapper().update(dirty_object);

也许

Repository.update(dirty_object);

更好?

  回复  引用    
#49楼 2007-10-16 04:03 怪怪      
LS在博客园也注册一个吧 :)

另外你的博客能否少些对国外动态的介绍, 多些自己的心得? 感觉你的项目的理论和实际设计经验都很丰富, 我觉得这对大家都很需要 :)

  回复  引用  查看    
#50楼 2007-10-16 04:30 deerchao      
这样分层思路是很清晰的,但是贫血,领域对象层和数据存取层之外通常还要再加个业务规则层。
如果用户界面上采用了MVC模式,那上面的这些都可以算作Model,再加一个Controller "层"和"View" 层,思路是完美了,可用了这么多的“层”,我怎么觉得有点牛刀杀鸡的感觉?

这种情况下,如果领域对象需要加一个字段,好像没有一个层不需要修改的,你觉得呢?

  回复  引用  查看    
#51楼 2007-10-16 07:15 dudu      
@怪怪
嗯。

  回复  引用  查看    
#52楼 2007-10-16 12:34 henry      
@deerchao
楼主的理论是正确,你说的只是实现和设计的问题.
ORM组件的设计都会集成一个mapper来处理所有model,但由于不同model的操作是不同所以引用了操作描述机制,描述方式用xml或attribute.

  回复  引用  查看    
#53楼[楼主] 2007-10-16 13:06 1-2-3      
@Anders06
POEAA 挺经典的。不过和《设计模式》一样,它不会对模式背后的设计思想和设计原则作更多的说明。我觉得《深入浅出设计模式》最好的地方就是将模式放在了一个具体的情境中并能与设计原则联系起来。俺写这篇文章也是想做些这方面的尝试,希望能够抛砖引玉,得到更多达人的指点。

  回复  引用  查看    
#54楼[楼主] 2007-10-16 13:09 1-2-3      
@lovecherry
谢谢您的鼓励。俺写得慢还有一个原因,就是肚子里墨水比较少,经常要搜呀刮呀的,也比较费时间^_^

  回复  引用  查看    
#55楼[楼主] 2007-10-16 13:11 1-2-3      
@dudu
谢谢。感动ing...

  回复  引用  查看    
#56楼[楼主] 2007-10-16 13:33 1-2-3      
@deerchao
以我这几年的经验来看,界面和流程是比较容易发生变化的,而这种变化对C和V影响比较大,而对M以及数据库影响较小,所以MVC大行其道是有道理的。有一个设计原则不是说“要分离系统中异变的部分与不易变化的部分”么?最要命的是业务发生变化,例如以前是一次性全额付款,现在要改成分期付款。这样从数据库到页面都要改。不过在这种修改不仅仅是追加某些功能或删除某些功能,而是一项与系统其它功能模块有很大关联的修改。如果系统有清晰的层次结构,就可以以更小的步骤进行迭代开发,会更容易些。
呵呵,刚刚作了4小时火车,思路比较乱,自己都不知说了些什么,请见谅:)

  回复  引用  查看    
#57楼[楼主] 2007-10-16 14:27 1-2-3      
真奇怪,刚刚有个“太差了吧”的评论,怎么转眼间就不见了?我并没还有删呀(我的原则是只删广告回帖)。
  回复  引用  查看    
#58楼 2007-10-16 16:44 dudu      
@1-2-3
是我删除的,最近有一些故意捣乱的评论。

  回复  引用  查看    
#59楼[楼主] 2007-10-16 16:56 1-2-3      
喔,谢谢。
  回复  引用  查看    
#60楼 2007-10-17 08:18 caidehui      
为了解决后面MapperRegistry使用代码增加Domian对象与Mapper对的问题,解决办法有2个
1.使用配置文件,将他们对应关系、所在Assembly等放进去,在MapperRegistry中通过读入配置文件,从而构建他们的关系。在大型工程中,最好是使用一个配置管理工具来构建配置文件。
由于我们要考虑很多人分工合作,都要去改这段代码是不行的。

2.使用默认规则(不推荐),将相关的Assembly动态加载进来,定义规则例如:Person的Mapper 就必须定义为PersonMapper,那么通过Assembly动态加载进来后,进行配对匹配就好了。

当然还可以做的更技巧一点。

还有一个问题就是MapperRegistry在Web应用中使用时应该如何放置的问题,既然要使用缓存、那么就可以放到Session的缓存中。

  回复  引用  查看    
#61楼[楼主] 2007-10-17 08:48 1-2-3      
@caidehui
使用配置文件应该是一个不错的方法。

> 还有一个问题就是MapperRegistry在Web应用中使用时应该如何放置的问题,既然要使用缓存、那么就可以放到Session的缓存中。
单件类的实现方法一直在困扰着我。因为在Web程序中,单件类的实例应该保存在Session中;而在WinForm程序中,只要放在一个static field中即可。我一直没有找到一个统一的实现方法。难道要为Web程序和Win程序分别实现两个MapperRegistry?

  回复  引用  查看    
#62楼 2007-10-17 16:24 钱彦云      
在以前我做的.net应用多仿照petshop建框架,一般会建Model project类似文中domain model,DAL project类似文中Mapper;BLL project实现业务逻辑同时实现facade模式。BLL和DOMAIN,DAL有单向依赖关系,和文章中的彩图一样,不同的是彩图中是Default.aspx.cs表现层做为client,页面的后台代码和BLL依赖,这样如果要同时增加department和person,BLL层要new Model和new DAL,并写相应的代码,代码还是比较麻烦,重复性比较高,可以尝试用文中方法改良一下我们的做法。而且我觉的MapperRegistry的建立减少了不断new domain model和new Mapper的性能损耗。下次我会在项目中应用一下这种考虑。谢谢你的文章,昨天的回复有个地方不准确,今天改了。
  回复  引用  查看    
#63楼 2007-10-18 15:14 小生      
怪怪
RicCC: @怪怪
预加载是一种不错的想法,不过如何做到按需加载,又跟复杂的领域模型解耦,足以难倒一片人
--------------------------------------------------------
這是我一直在思考的問題--如何保持領域模型的"純潔" ﹕)

順便贊樓主的文章﹐行文流暢﹐讀來行云流水﹐賞心悅目

  回复  引用  查看    
#64楼[楼主] 2007-10-18 15:21 1-2-3      
@钱彦云
也推荐您看《企业应用架构模式》,这本书真的是很经典的。

  回复  引用  查看    
#65楼[楼主] 2007-10-18 15:23 1-2-3      
@小生
谢谢您的鼓励。繁体,难道是在台湾?

  回复  引用  查看    
#66楼 2007-10-18 17:00 小生      
@1-2-3
台資﹐港資都是繁體...
在這里呆久了都已習慣叫數據庫為資料庫了﹕)

  回复  引用  查看    
#67楼 2007-10-19 11:44 钱彦云      
呜呜呜,早就想买了,china-pub一直断货。
  回复  引用  查看    
#68楼 2007-10-19 12:41 紫色阴影      
@钱彦云
呵呵 建议看影印版

  回复  引用  查看    
花一个星期写出来的和花半个小时写出来的确实不一样,应该向博主学习
  回复  引用  查看    
#70楼[楼主] 2007-10-22 13:24 1-2-3      
@亚历山大同志
谢谢您的鼓励。您有一篇文章的图形用的是“打印内容+手绘边框和连接线”的方法,真的是很有创意呀,是不是用OneNote做的?

  回复  引用  查看    
写得不错。思路清晰
  回复  引用    
#72楼 2007-10-30 16:11 分享      
写得很好!

感觉这个架构对比较大型的系统有参考价值,完全隔离开Domain和数据库,可以考虑在Mapper用hibernate.

小型系统就使用Active Record就可以了。我目前的系统就是直接用hibernate映射数据库里面的Entity.

  回复  引用  查看    
#73楼 2007-12-29 15:48 ifire[未注册用户]
--引用--------------------------------------------------
1-2-3: @caidehui
使用配置文件应该是一个不错的方法。

&gt; 还有一个问题就是MapperRegistry在Web应用中使用时应该如何放置的问题,既然要使用缓存、那么就可以放到Session的缓存中。
单件类的实现方法一直在困扰着我。因为在Web程序中,单件类的实例应该保存在Session中;而在WinForm程序中,只要放在一个static field中即可。我一直没有找到一个统一的实现方法。难道要为Web程序和Win程序分别实现两个MapperRegistry?
--------------------------------------------------------

配置文件有什么好的?在配置文件中定义和代码中定义有什么本质的区别么?

www.infoq.com/cn/上有一个Singleton的web/win 统一实现可以参考

btw:文章写的不错(我是写不出来的),domain model的实现有很多细节问题需要探究,期待后续文章能有所体现,呵呵

  回复  引用    
#74楼[楼主] 2007-12-29 16:07 1-2-3      
@ifire
非常感谢您的回复。
www.infoq.com/cn/上的那篇文章是不是这篇http://www.infoq.com/cn/articles/fine-grained-singleton-pattern" target="_new">http://www.infoq.com/cn/articles/fine-grained-singleton-pattern
它的方法是使用如下的代码来判断是否是Web程序:
/// 判断当前应用是否为Web 应用的Helper 方法(非官方方法)
private static bool CheckWhetherIsWeb()
{
bool result = false;
AppDomain domain = AppDomain.CurrentDomain;
try
{
if (domain.ShadowCopyFiles)
result = (HttpContext.Current.GetType() != null);
}
catch (System.Exception){}
return result;
}

  回复  引用  查看    
#75楼 2008-01-23 15:04 dyh[未注册用户]
学习了。
  回复  引用    
#76楼 2008-05-26 16:35 私家侦探      
MapperRegistry 是简单工厂不是工厂方法的变形
工厂方法其实是简单工厂的加强版
抽象工厂又是工厂方法的加强版
一次比一次复杂,解决的问题也更大更好

lz说到后面变成简单工厂了,简单工厂可是违背了开放封闭原则啊,所以一般简单工厂要配合反射技术使用呵呵

  回复  引用  查看    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

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

0 917762




相关文章:

相关链接: