UML软件工程组织

 

 

关于领域模型
 
2008-03-14 作者:Joey 来源:cnblogs.com
 

对于领域模型这个概念,以前没有系统性的认识,只是根据经验,在设计系统时自发的在使用.尤其是O'R Mapping技术成熟并且逐渐成为主流以后,这种模型化的设计方法在项目应用中体现得非常之多.

http://forum.javaeye.com/viewtopic.php?t=17579

在JavaEye的这个帖子中,大牛robbin总结了4种常见的领域模型,并分析了它们的优缺点.

1、失血模型

2、贫血模型

Service(业务逻辑,事务封装) --> DAO ---> domain object

3、充血模型

Service(事务封装) ---> domain object <---> DAO

4、胀血模型

domain object(事务封装,业务逻辑) <---> DAO

其中失血模型与贫血模型的主要区别在于“domain ojbect包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到Service层”。 好理解点的表达就是:如果某个逻辑不依赖于DAO来实现,就把它放到domain object里面,否则就在Service层实现。

讨论的结果,robbin倾向于使用基于良好设计和实现技术下的贫血模型。

这是以前做过的一个Java项目,典型的失血模型,db->bean里面是按照模块划分的实体bean,也就是只含有getter和setter的domain ojbect。manager就是DAO,使用Hibernate做持久化。做业务逻辑和事务封装的Service封装在Action里面,使用Struts做MVC控制。
在使用里面,这种模型的domain ojbect和DAO实现基本都是靠工具来生成了。程序员可以更关注具体的Action实现。

另外,在最近的一个项目里面,我使用了一种比较畸形的设计模型,
domain ojbect的实现:
 

Domain Object示例
  1/**//// <summary>
  2    /// This code is generated by PersistentObjectCodeGenerator
  3    /// </summary>
  4    [Table("Company_Info")]
  5    public class Company_Info : DBO.Persistence.PersistentObject
  6    {
  7        [PrimaryKey(DbType.Int32, "CompanyId", AutoGenerated=true)]
  8        private int _CompanyId;
  9        [Column(DbType.AnsiString, "CompanyName", Nullable=false)]
 10        private string _CompanyName;
 11        [Column(DbType.AnsiString, "CompanyLogo")]
 12        private string _CompanyLogo;
 13        [Column(DbType.AnsiString, "CompanyArea")]
 14        private string _CompanyArea;
 15        [Column(DbType.String, "CompanyInfo")]
 16        private string _CompanyInfo;
 17        [Column(DbType.Int32, "CompanyAddedBy")]
 18        private int _CompanyAddedBy;
 19        [Column(DbType.Boolean, "IfChecked", Nullable=false)]
 20        private bool _IfChecked;
 21        [Column(DbType.Date, "CompanyPubDate", Nullable=false)]
 22        private System.DateTime _CompanyPubDate;
 23        [Column(DbType.Int32, "NewsCount")]
 24        private int _NewsCount;
 25        [Column(DbType.Int32, "BSCount")]
 26        private int _BSCount;
 27        [Column(DbType.Int32, "MSCount")]
 28        private int _MSCount;
 29        [Column(DbType.Int32, "BBSCount")]
 30        private int _BBSCount;
 31        public virtual int CompanyId
 32        {
 33            get
 34            {
 35                return _CompanyId;
 36            }
 37            set
 38            {
 39                // Use SetFieldValue to assign a column field, not the "=" operator
 40                this.SetFieldValue("_CompanyId", value);
 41            }
 42        }
 43        public virtual string CompanyName
 44        {
 45            get
 46            {
 47                return _CompanyName;
 48            }
 49            set
 50            {
 51                // Use SetFieldValue to assign a column field, not the "=" operator
 52                this.SetFieldValue("_CompanyName", value);
 53            }
 54        }
 55        public virtual string CompanyLogo
 56        {
 57            get
 58            {
 59                return _CompanyLogo;
 60            }
 61            set
 62            {
 63                // Use SetFieldValue to assign a column field, not the "=" operator
 64                this.SetFieldValue("_CompanyLogo", value);
 65            }
 66        }
 67        public virtual string CompanyArea
 68        {
 69            get
 70            {
 71                return _CompanyArea;
 72            }
 73            set
 74            {
 75                // Use SetFieldValue to assign a column field, not the "=" operator
 76                this.SetFieldValue("_CompanyArea", value);
 77            }
 78        }
 79        public virtual string CompanyInfo
 80        {
 81            get
 82            {
 83                return _CompanyInfo;
 84            }
 85            set
 86            {
 87                // Use SetFieldValue to assign a column field, not the "=" operator
 88                this.SetFieldValue("_CompanyInfo", value);
 89            }
 90        }
 91        public virtual int CompanyAddedBy
 92        {
 93            get
 94            {
 95                return _CompanyAddedBy;
 96            }
 97            set
 98            {
 99                // Use SetFieldValue to assign a column field, not the "=" operator
100                this.SetFieldValue("_CompanyAddedBy", value);
101            }
102        }
103        public virtual bool IfChecked
104        {
105            get
106            {
107                return _IfChecked;
108            }
109            set
110            {
111                // Use SetFieldValue to assign a column field, not the "=" operator
112                this.SetFieldValue("_IfChecked", value);
113            }
114        }
115        public virtual System.DateTime CompanyPubDate
116        {
117            get
118            {
119                return _CompanyPubDate;
120            }
121            set
122            {
123                // Use SetFieldValue to assign a column field, not the "=" operator
124                this.SetFieldValue("_CompanyPubDate", value);
125            }
126        }
127        public virtual int NewsCount
128        {
129            get
130            {
131                return _NewsCount;
132            }
133            set
134            {
135                // Use SetFieldValue to assign a column field, not the "=" operator
136                this.SetFieldValue("_NewsCount", value);
137            }
138        }
139        public virtual int BSCount
140        {
141            get
142            {
143                return _BSCount;
144            }
145            set
146            {
147                // Use SetFieldValue to assign a column field, not the "=" operator
148                this.SetFieldValue("_BSCount", value);
149            }
150        }
151        public virtual int MSCount
152        {
153            get
154            {
155                return _MSCount;
156            }
157            set
158            {
159                // Use SetFieldValue to assign a column field, not the "=" operator
160                this.SetFieldValue("_MSCount", value);
161            }
162        }
163        public virtual int BBSCount
164        {
165            get
166            {
167                return _BBSCount;
168            }
169            set
170            {
171                // Use SetFieldValue to assign a column field, not the "=" operator
172                this.SetFieldValue("_BBSCount", value);
173            }
174        }
175    }


这个是靠工具实现的实体类的代码,其中使用的Attribute是由于我使用的持久化实现不是从配置文件而是通过反射从元数据中读取的。
DAO实现:
 


 1public class DBManager
 2    {
 3        public static bool Add(PersistentObject obj,string ConnStr)
 4        {
 5                            
 6        }
 7
 8        public static PersistentObjectCollection Select(string sql,string ConnStr,System.Type objType)
 9        {
10        
11        }
12
13        public static bool Delete(PersistentObject obj,string ConnStr)
14        {
15    
16        }
17
18        public static bool Update(PersistentObject obj,string ConnStr)
19        {
20            
21        }
22
23        public static bool RunSql(string sql,string ConnStr)
24        {
25            
26        }
27    }

这其实是个通用的DAO类,只是实现了通用的ADSU方法,外加一个RunSql实现。没有设计针对具体的domain ojbect 实现具体业务逻辑的Service Objects,而是把剩下的事情,交给客户(web层代码消费者)了。
先别骂我,先看看这样的设计,是基于一种什么背景:
1、这是个所谓的web2.0网站,需要以半天为周期的进行系统更新与功能升级。
2、底层的基本数据结构不会发生变化。
3、在系统升级过程中,需要尽可能多的减少升级时间成本。
4、开发团队人员很少(1-2人),web层开发人员本身就参与底层库编码,因此,向web层开发人员隐藏底层对象没有意义。
5、新的用户功能是难以在设计时考虑周全的,甚至可能是每天都有增加或者变更。
个人认为在这种情况下,这种设计模型基本可以满足项目的需要了。需要注意的是:DAO和domain ojbect,都是独立的编译单元,在系统更新时,是无需考虑它们的。


结合我的项目经验,在认真读了JavaEye上的讨论之后,我感觉有些地方也有一些其它思路:

所有这些设计中,可能胀血模型是最符合OO的了。但是胀血模型应该也是实现成本最高,风险最大的一个模型。原因很简单,domain ojbect模型的不稳定带来的成本会非常痛苦。
因此,一个项目设计的好坏,是否不应该单纯看其设计思路是否符合OO原则,而应该考虑这个设计给项目带来的整体实施成本。相比之下,失血模型和贫血模型的domain ojbect层非常稳定(基本可以使用工具生成了),而且编写相对很简单,复杂的业务逻辑交给Service去实现,大家分工明确,不同编码水平的coder可以很好的配合起来,完成项目的高质量实现。而胀血模型中,对domain ojbect的编写显得非常重要,因为domain ojbect本身就包含了事务封装和业务逻辑。
另外大胆说一句,是否OO有时并不取决于我们的设计,而是要受技术条件的限制的。假设数据库本身可以提供的不是关系型数据而是对象集,假如我们不需要DAO编码,设计本身,会更加漂亮。

以上是我的一些体会和思考,记录下来。

另外向诸位交流几个问题:
1、在DAO实现中,有没有在DAO接口与DAO实现设计方面比较有经验的兄弟,介绍一下这样设计的好处?
我在项目中基本没有过DAO接口设计的经验,都是直接做实现,虽然使用接口可以使系统不依赖于某种具体的持久化实现方法,但是目前为止,还没有遇到过需要在项目中做更换持久化方法这种大手术的情况。况且额外增加一个接口层,还是蛮麻烦的。

2、在.net里面,大家常用的持久化工具有哪些?除了NHibernate?

评论
javaeye确实不错,先去学习学习楼主说的4种模型。
#2楼 [楼主]
@Cure:
其实这4种模型中的一种甚至多种,我们在开发中已经在经常自觉不自觉地使用了。所以说牛人之所以牛,是因为它们善于总结,把习惯型行为归纳为经验和条款。便于日后对设计经验的复用,呵呵,这点,我是只有佩服的份儿了。
经常写写blog的好处,也是帮助自己总结吧。。
业务模型是从客户的业务中总结出来的,对应业务概念。业务模型的建立只能依靠人的头脑分析,需要和客户反复讨论,绝不可能自动生成的。如果一段代码可以用工具自动产生,那么他肯定不是domain object。如果硬要把这样的代码当作domain object,这样的domain object肯定丧失了很多业务含义。所丧失的含义肯定要在其他的某个位置补偿回来,于是这些业务就会分散到用户界面中。
#4楼 [楼主]
我认为这主要取决于我们看待domain object的角度。我理解你的意思,应该是比较支持胀血模型的设计思想.的确从语义的角度,domain object不能仅仅包括对业务对象的静态描述,还应该包括业务对象的行为描述,但是实际操作中,这种模型很难得到较好的实现.所以我的核心体会就是:应该选择最适合这个项目的设计模型,而不是理论上最"美"的模型.

不知道对你的意思理解有没有偏差,感谢你的留言,希望能与你继续探讨.
DAO多体现于SOA模式。
 
这个概念太理想化了,实际上基本上做到,domain的作用就是想把业务逻辑和操作数据的逻辑合到一起,表达一个 业务对象->业务数据对象 的目的,只有3,4算得上领域模型,1,2只是提出者为了更好的解释领域模型而凑上去的
#7楼 [楼主]
贫血模型理论上的主要缺陷:

1:Item和ItemManager实际是操作与数据的关系,实际完成的就是经典OO中的一个对象的能力;
2:当有许多Item时 类组变得很庞大,产生很多 xxxDao xxxImpl xxxManager 其中包含大量重复代码;
按<<重构>>的观点,上述代码存在以下臭味:
1:重复的代码 xxxDao xxxImpl xxxManager(通常)
2:霰弹式修改,一个变化影响多个类,类之间不够高内聚 item变化-->Dao,Impl,Manager均要变动
3:依恋情结,两个类之间互相作用过多 item<->Manager
4:平行继承体系,当增加一个新类时总是要增加另一个类
5:夸夸其谈未来性,在没有任何暗示的情况下考虑扩展 Dao,实际HibernateImpl可能n年内是唯一的Dao实现
6:纯稚的数据类,只有数据的类 item

转载自http://blog.donews.com/ooFrank/archive/2006/04/10/821788.aspx
有提到ruby on rails,属于胀血模型,因为它把domain object和DAO都合并了。
ROR应该是居于表模块+活动记录模式的吧?
 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号