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


     

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

     

    【问题】

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

    图表 1 基本体类结构

     

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

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

     

    图表 2 属性类与基本体类关系

    图表 3 CAD示例

     

    在写了N多相似的类后未免让人厌倦,让人有种重构的冲动。实现界面与数据分离可以使用脚本的办法,在此不作赘述

     

    【解决方案】

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

    图表 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的实现可以参考GoFMemento Pattern.

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

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

     
  • 相关阅读:
    426 根据gop 讲解x264整个过程
    426 x264全局 完整有些东西还没理解
    SAD SATD转过来的
    帧内预测1
    4.1总结
    426 大话dct
    426 pixel赋值问题 mbcmp函数 宏定义
    426 pts dts
    每天进步一点点IAR for ARM_V6.30.1
    每天进步一点点NIOS II按键中断程序
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333140.html
Copyright © 2020-2023  润新知