“这就象花一样。如果你爱上了一朵生长在一颗星星上的花,那么夜间,你看着天空就感到甜蜜愉快。所有的星星上都好象开着花。”
                                                                                    ——圣埃克絮佩里
                                                                                          摘自《小王子》
摘要

使用关系限定符可以
    - 更精确地表达实体关系
    - 直接指导实现代码的编写
    - 使领域模型更简单、更容易理解

让我们观察一个销售信息系统的设计,看看关系限定符有什么用。

领域知识:一种产品有一个价格

一种产品有一个价格,这个就算不问领域专家,也是人人都知道的常识。数据库表结构如下。


如果你只看到价格表引用了产品表的主键,并不能肯定产品表与价格表是一对一还是一对多的关系,所以我们还在图中加上了一个注释。
使用UML图会稍稍爽一点,可以使用关联来表示一对一的关系。

示例数据如下。


新的领域知识:每年要有一套价格表

与领域专家进一步沟通之后,开发人员得知,客户公司会每年制定一套价格表,对于不同时间签订的销售合同,将可能使用不同年度的价格;但是每种产品每个年度只能有一个价格。看下面的数据会更清楚些。


我们需要为价格表增加一个年度字段,并修改注释。数据库表结构如下图所示。


UML图该如何修改呢?产品与价格变成了一对多的关系,可以使用聚合来表示。


但是,这里没有表现出“一种产品每个年度只有一个价格”这个领域知识。在这种情况下,我们可以使用关系限定符来更精确地表达产品和价格的关系。


年度框框到价格框框的关联表达了“一个年度只有一个价格”这一领域知识。年度框框脑袋上面的那个“*”号表示一种产品可以有多个年度(所对应的不同的价格)。

关系限定符如何对应于实现代码

1. 产品实体类可以使用一个类型为IDictionary的属性关联价格实体类。
public class 产品 
{
    
    
private IDictionary<int, 价格> _价格s;
    
public IDictionary<int, 价格> 价格s
    {
        
get { return _价格s; }
        
set { _价格s = value; }
    }
}

当我们想知道AK47的2008年度的价格时,就可以使用类似于下面的这段代码。
产品 AK47 = new 产品Finder().Find产品ByName("AK47");
decimal price = AK47.价格s[2008].标准价格;

2. 可以为价格Finder添加一个 FindBy产品Id年度() 函数。
产品 AK47 = new 产品Finder().FindByName("AK47");
价格 AK47价格 
= new 价格Finder().FindBy产品Id年度(AK47.产品Id, 2008);
decimal price = AK47价格.标准价格;
FindBy产品Id年度()函数的返回值是一个价格实体类对象,而不是 IList<价格>,清楚地表达了“一种产品每个年度只有一个价格”这一领域知识。

3. 关系限定符的存在暗示了在插入、修改价格表数据前应该验证“产品Id,年度”在价格表中是唯一的。
// 判断 产品Id、年度 是否已存在。(用于插入数据前的验证)
public bool Is产品Id年度Exist(产品Id,年度);
// 判断 产品Id、年度 是否已存在(不包括价格Id所在的那条数据)。(用于更新数据前的验证)
public bool Is产品Id年度ExistExcept价格Id(产品Id,年度,价格Id);


一些题外话

1. UML图比ER图更有表现力么?

是的。比起ER图,UML图可以更加精确、自然地表达实体关系,以本文为例,主要体现在:
    a) 从产品实体类到价格实体类的单向关联表达了“系统只关心某个产品的价格是多少,不关心价格是2500的都有哪些产品”这个领域知识。
    b) 使用关系限定符可以更精确地表达一对多关系在某种限制条件下的一对一关系。
    c) 虽然都能表达一对多关系,但是从产品实体类到价格实体类的聚合怎么都感觉比价格表引用产品表的主键的这种表现方式更加自然。

2. 画了UML图就是面向对象的设计了么?

    这个当然不是啦。我们都知道武功包括招式和心法,二者缺一不可。如果只知招式而不知心法,则招式全无用处。某些特殊情况下可能打出来的是打狗棍法的招式,暗地里却是在运用全真剑法的心法,这时本质上其实是在使用全真剑法。所以建模的时候画出标准、漂亮的UML图固然不错,但是真正重要的是图形所表达的内容,这又仰仗于是否使用了正确的心法。
    有的武功,甚至相同的招式却有两套心法,应该使用哪一套要视情况而定,如果用错了心法,不但不能克敌制胜,反而会死得比不会武功的人还惨!当然,这些内容已超出本文的讨论范围,有兴趣的朋友可以看本文的续篇《建模心法》

 

参考文献

Martin Fowler 著,徐家福 译,UML 精粹(第2版)标准对象建模语言简明指南。清华大学出版社,2002。
Eric Evans, 领域驱动设计(影印版)。人民邮电出版社,2007。
金庸,神雕侠侣。1976。

 

posted on 2008-08-04 08:32  1-2-3  阅读(7124)  评论(15编辑  收藏  举报