构建通用类型- 继承 VS 聚合

构建通用类型- 继承 VS 聚合

继承和聚合的比较GoF[1]做了详尽的阐述,在此偶将从实践的角度用一个例子来提供一种比较通用的解决方案,对继承和聚合做一个适用本案例的选择。此文乃一个案,并不代表两者的绝对优劣,具体问题还是要具体分析。

 

【问题】

在CAD或画图软件设计设计中,会存在大量的基本体[2],如line、 circle、arc、 polyline、 sphere、 box等。在组织它们之间的关系的时候,一般会有如下的继承体系:

构建通用类型- 继承 VS 聚合_第1张图片

图表 1 基本体类结构

 

即对每一种基本体,都有一个class与之对应。若有100种基本体,那就得有100个class从Entity继承而来。

但从这些class本身来看,问题并没有多么严重,毕竟不同的基本体总得有个class或object异或别的什么东西与之对应。一个完整的画图软件总得有序列化机制、总得有个undo/redo机制、总得有个界面显示这些纷纷乱乱的基本体的参数以供交互式操作吧。因此,每个类又得加入save/load操作,undo/redo支持,再加上套UI class[3]。记得N年前做的一个CAX项目中,但为基本体写UI class就花了2W行代码,现在想想有些汗颜。

构建通用类型- 继承 VS 聚合_第2张图片
图表 2 属性类与基本体类关系

 

构建通用类型- 继承 VS 聚合_第3张图片

 

图表 3 AnyCAD.Demo示例

 

在写了N多相似的类后未免让人厌倦,让人有种重构的冲动。实现界面与数据分离可以使用脚本的办法,在此不作赘述,感兴趣的可以参考AnyCAD.Demo ( http://www.codeplex.com/anycad )。

 

【解决方案】

对于一个绘图程序,其基本体主要有两部分内容:参数 + 构建方法。各种基本体之间的差异也存在于此。数据部分可以用一个variant[4]容器统一来描述,通过一个参数ID提取不同的参数。差异最大在于创建实体的方法,即作用于参数的行为。再次抽象后,通用基本体类型层次关系如下:

构建通用类型- 继承 VS 聚合_第4张图片

图表 4 通用基本体类型类结构

 

各种基本体类型都用Primitive统一描述,不再从它继承子类,而是实现不同的创建基本体的BuildMethod。最后用一个Factory模式管理各种BuildMethod,根据与Primitive绑定的BuildMethod ID,创建几何形状。

 

由此所带来的好处是:

1.       Save/Load只需要在Primitive实现

2.       Undo/Redo只需要在Primitive实现

3.       可以从Primitive的参数类型描述中提出GUI表现形式[5]

4.       可扩展性得到增强,新增的类型无需考虑以上三条。

 

由此所带来最大的坏处就是性能上有所损失。从variant得到具体的数据类型毕竟没有直接用原始的数据类型来的高效些。从使用的效果来看,并不会产生用户交互上的延迟。

 

【总结】

         前后两种方法的主要差别在于是使用继承还是聚合,以及是如何使用继承的。二者本身没有优劣之分,但在具体的情景下就需要权衡利弊。

        

若有不当,欢迎扔砖扔玉,特此声明!

--- 力为

 

[1] 《设计模式》

[2] 指基本几何类型。不知道这世界上有多少基本体,在不同的领域中可能基本体的定义也不尽相同。建筑中,可能门,窗都算作是基本体。

[3] boost::serialization 提供了一种非侵入式的序列化方法,值得一试。

Undo/redo的实现可以参考GoF的Memento Pattern, AnyCAD的undo/redo演示效果见这里。

UI层通常需要跟数据层分离。AnyCAD.Demo使用了Lua脚本为基本体配置UI属性表,源代码见这里。

[4] variant的实现有多种方法,boost有两种,参考boost::any 与 boost::variant 的区别。

[5] 提取形式亦可参考如何从脚本提取UI的实现方法。

你可能感兴趣的:(构建通用类型- 继承 VS 聚合)