细说继承关系映射

最近看了N多文章都讲继承的三种实现,最初是由浅入深Hibernate,接着是Teddy的关系总述,最后是孙亚民的《框架》。
这些文章所描述的实现方式都一模一样:三种。第一种是整个体系一张表;第二种是每个具体类一张表;第三种是每个类一张表。继承是OO技术,设计数据库表则是数据库技术,所以就称之为关系映射技术,我觉得称之为元数据设计比较更合理一点,讨论的基本上都是关于“抽象”的方法。我写过一篇讲元数据设计的文章,比较抽象一点,现在仅仅针对继承,可以“细”一点说。其实我对Hierarchical研究得更透一些,正在筹划一篇超长的文章来“细说”这种特殊的关系,继承关系对我来说完全是业余。

一、映射方法的评估
第一种方式,基本上没有什么意义,我定义为“滥用Nullable特性”。除了无端添加了一个描述类型的属性外,这样的冗余简直无法忍受,所以我根本不想讨论。我不明白那么认真的老外们为什么会认为这也算一种实现继承的方式之一。
第二种方式比第一种方式要好一些,对对象层面来说就是在关系映射中放弃了继承,否认实体的部分共性。这样做最大的好处就是简化了数据库访问。对于简单数据类型的属性共享,通过表来分离虽然破坏了实体关系却获得了比较好的性能优势,大部分情况下还是合算的。假如实体A和实体B共享大部分属性,仅仅只是A的某个属性B没有,或者B的某些属性A没有,用这种方式应该比较平衡。在一种情况下不能使用:如果A和B共享某个关系,这种方式就无法完成了。例如,A和B同时被C引用。例如,组织机构是一个抽象实体,下辖部门。组织分为三种,政府组织、企业组织和民间团体。按照每个具体类映射一张表的方法,组织机构的问题是解决了,但是部门没法处理。根据我的经验,在数据库设计中继承关系存在的唯一理由就是关系共享导致的抽象。所以,第二种方式基本上不用考虑,原因是:根本没有实现继承,就是说这种方式实现的继承根本就用不着继承。
到此,映射继承的方法只剩下最后的一种了。事实上,第三种方法的确比较通用一些,几乎可以处理各种可能出现的情况。我所知道的无法处理的情形之一是跨数据库的抽象。上例的组织关系通过增加一张组织机构表,部门只和组织机构发生关系即可。这种方式很容易理解。

二、展开的继承关系
第一种展开:一拆再拆。
事实上,在领域中遇到的关系远非组织和部门关系这么简单。假如,我们在上例中再增加一个实体:个人,并且再假定个人隶属于部门。听起来这是一个聚合关系,当你发现组织机构可以没有部门的时候,你不得不把部门纳入组织机构的继承体系,同时还必须维持部门与组织机构间的聚合关系。其实事情还没有完。部门是可以分级的,直接隶属于政府组织、企业组织和民间团体的部门是顶级部门,它们的上级是独立的组织机构;而隶属于部门的部门与顶级部门之间还是有着很多的特异性,基于以上的讨论,需要将这两种部门再次抽象,拆分成三个实体:部门、顶级部门和下级部门。
第二种展开:隐藏的体系关系(Hierarchization)。
一般来讲,体系关系都是通过自引用来实现的,不关继承什么事儿,事实上更多的体系关系是通过继承关系配合聚合来实现的。这种体系关系非常隐蔽,却避免了通过自引用实现所带来的“根陷阱”。换句话说,根节点和枝叶节点并不是同一个实体,而是同一个抽象实体的两个具体实体。虽然,绝大部分设计师忽略了根节点的“某个属性必须为空”这样的冗余,但并不等于这个冗余并不存在。例如,实现一个文件夹的体系。文件夹是一个抽象类,根文件夹和子文件夹分别继承自文件夹,但子文件夹有一个上级文件夹这个属性根文件夹是没有的,这个属性并不指向子文件夹自己,而是指向抽象文件夹。其实这也是一种自引用,只是并不是直接引用自己而是引用自己的抽象实体。
第三种展开:并行的体系(Inheritation)。
我们通常将根抽象类、中间抽象类到具体类的地图称之为继承体系。由于“继承关系存在的唯一理由就是关系共享导致的抽象”,所以绝大部分继承体系不是孤立和封闭的,往往有另外一棵或多棵继承体系树伴随。例如,上例中的组织机构可能都有一个决策机构,而不同的组织机构其决策机构是不同的,就必然形成另外一棵决策机构继承体系树。需要考虑的问题是:整体上是决策机构引用组织机构,具体实体间的关系映射却非常复杂。基本上有两种:根关系和叶关系。抽象的决策机构引用抽象的组织机构则为根关系,具体决策机构引用具体组织机构则为叶关系。两种都可以,具体根据领域逻辑来定,但必须避免多重引用关系特别是隐藏的多重引用关系,导致关系混乱。

三、继承关系的限制
第一个限制是:在OO体系中,根类和中间类可以是抽象的,也可以是具体的,但是在映射到数据表之后却只能是抽象类。因为将同一张表中的具体类型实例与抽象类型实例很难分离。即使分离也是使用一些极不优雅的方法,例如增加一个标记字段或字段的某个标记域,违背了降冗优先的基本原则。所以,到叶一级很可能出现一些没有任何属性的实体,但是却完全没有了冗余。
第二个限制是:不能采用复合键。复合键一直是我极度深恶痛绝的东西,我实在不明白这样的东西存在的理由。复合键不同于组合键,组合键事实上还是单键,仅仅是必须以多个分离的字段来实现这个单键,映射到OO中其实就是两个双向的聚合(双元组合)或者六个双向聚合(三元组合),非常自然,也根本不会涉及到继承。复合键却不然。在派生类中通过复合键对应其超类,将极度困难!
第三个限制是:继承关系的维护问题。普通关系的维护非常简单,增加一个字段或者减少一个字段都没有什么问题。重构继承关系的映射简直就是一场恶梦,特别是如果还牵涉到历史数据的迁移,那简直就让人抓狂了!所以,如果关系不够稳定,要尽可以细地抽象,避免维护关系,以牺牲性能来换取可维护性。

posted @ 2005-12-01 02:22  双鱼座  阅读(3067)  评论(6编辑  收藏  举报