[面向对象] C++ 类层次结构的设计方法学
作者:Breaker <breaker.zy_AT_gmail>关于 C++ 类层次结构的设计方法学,note-to-self + keynote + cross-reference 式笔记本文精炼于 12.4, 15.2 的 BBWindow 示例,只涉及 designSyntax 参考 Ch12, 15; Ch17, 18
Play with bits 参考 5.2keyword: class hierarchy, multiple inheritance, abstract class, virtual base class, abstract factory, clone目录
[*]示例场景
[*]分离实现和接口
[*]替换实现
[*]重复基类
[*]共享实现
[*]抽象工厂
[*]Clone 模式
示例场景^IValBox: 取得用户输入整数的 GUI 元素之抽象类,它不绑定具体 GUI 元素,如 slider 滑块, dial 拨盘IValSlider: 滑块式 IValBox 实现,类似的还有 IValDial 拨盘式实现,代表 IValBox 类层次中具体的 GUI 元素。这些类可以进一步扩展,如从 IValSlider 派生出 PopupIValSliderBBWindow: 第三方提供的 GUI 元素实现,类似 MFC 的 CWnd 等。IValBox 的类层次依靠 BBWindow 的类层次实现 GUI 特性(画图之类)。BBWindow 只是形式名,它可以替换,其意义就像将 MFC 换成 Qt 一样,这使得 IValBox 的类层次能够减小对特定 GUI 元素实现的依赖UML: Old Hierarchyhttp://my.csdn.net/uploads/201205/27/1338112451_7381.png设计要义:
[*]使用 BBWindow 不是 IValBox 概念的基本部分。过分依赖 BBWindow,使得 BBWindow 难于替换
[*]可变数据是实现的部分,当它侵入接口(抽象类)时,会影响接口的灵活性
[*]Two-type interfaces: public interface vs. protected interface
interface: 有译接口,有译界面;有时代表函数,有时代表函数之聚集处(类、名字空间),凭上下文判断。按 Item 18 的说法:每一种接口都是客户与你的代码互动的手段
public interface: 给用户使用
protected interface: 给派生类使用
Strict Guide: Data members are better kept private so that writers of derived classes cannot mess with them.
推论: A protected interface should contain only functions, types, and constants.
更多 private data member 的讨论见 Item 22
Over strict?
Old Hierarchy 是不是 bad design?
实际上我用 MFC 时,大多数时候都这样做,这是大多数人用 GUI 框架的方法
问题不在绝对的 bad design 或 good design,而在于目标是什么?Application Development vs. Library Development, Cross-platform vs. Dedicated-platform
下面的改进设计比 Old Hierarchy 灵活而光鲜,但也带来的额外的负担(如理解和维护),Pros and Cons 自行抉择
分离实现和接口^UML: Separate Implementation and Interfacehttp://my.csdn.net/uploads/201205/27/1338112485_5226.png接口线:接口继承形成的层次
实现线/扩展线:实现继承形成的层次设计要义:
[*]public 继承 vs. protected/private 继承
另一种表述是:接口继承 vs. 实现继承,见 Item 40。注意 Item 34 和这个设计无关,虽然标题相同,但那个是 for member function 的,这里是 for class 的
public 继承塑模 "is-a" 的关系(见 Item 32),而 protected/private 塑模 "implemented-in-terms-of" 的关系(见 Item 39)
protected 和 private 的区别:private 之实现止于直接派生类,而 protected 之实现可以进一步扩展
[*]替代技术:public 继承 + Composition 复合
当用于实现域时,复合塑模 "implemented-in-terms-of" 的关系(和 protected/private 继承相同),见 Item 38
采用 protected/private 继承还是复合的判断,见 Item 39,如继承会造成 EBO (Empty Base Optimization) 空白基类最优化
替换实现^这是分离实现和接口后得到的益处UML: Substitute Implementationhttp://my.csdn.net/uploads/201205/27/1338112502_1715.png图中的 BBSlider 表示 BBIValSlider 可藉由以存在的更特定的 GUI 元素实现,而不是更一般的 BBWindow,就像是从 MFC 的 CWnd 改为了 CSliderCtrl使用同样的方法进行扩展就能得到 Big One:UML: Substitute Implementation, Complicatedhttp://my.csdn.net/uploads/201205/27/1338112522_8939.png无需纠结上图具体含义,这里的设计推论更有意思:一个系统展现给用户的应该是一个抽象类的层次结构,其实现所用的是一个传统的层次结构
有点教条吧?试问,传统的层级结构从何而来?我们要做一个横跨多种 GUI 框架之上的框架么?重复基类^用 virtual 基类消除 replicated base class 重复基类,见 Item 40这种用法的目的有二:
[*]粘合 (glue) 两个不相干的类。这是我们应该忘掉的 virtual 多继承,连 Bjarne 也说它是 crude, effective, and important, but not very interesting.
[*]为了下面的共享实现,这是有逻辑意义的
共享实现^UML: Share Implementationhttp://my.csdn.net/uploads/201205/27/1338112539_5592.png这里 Diamond-shaped Inheritance 钻石形继承的意义:让实现 PopupIValSlider 的类(即 BBPopupIValSlider)共享实现 IValSlider 的类(即 BBIValSlider)中的实现,以减少编码于是另一个有意思的推论:组成应用之接口的抽象类的所有派生都应该是 virtual 的, Item 40 也说 public 继承应该总是 virtual
但是现实不能如此,最简短的反驳是效率因素,见 15.2.5 和 Item 40可藉由无 data member class 进行重复基类方式的优化
于是,饶了一圈又回来了抽象工厂^构造函数不可能是 virtual 的,道理很简单:不知道对象的确切类型,又如何构造它(构造函数的实质是对象内布局的 bits 初始化)抽象工厂和 Clone 模式被戏称为 virtual constructor 虚拟构造函数,因为它们用 virtual 函数迂回完成构造函数的任务:根据某些线索创建对象
[*]对于真的构造函数,线索是构造函数之参数
[*]对于抽象工厂,线索是程序初始化时的预先设置。它的形态(典型的)可以是类的 UUID 标识 (e.g. COM's IClassFactory)
[*]对于 Clone 模式,线索是当前对象之确切类型
绝对的抽象创建(没有线索)是不可能的,即没有语法支持(virtual 构造函数),也没有逻辑意义:当你想要铅笔时,可以说我要铅笔,也可以说我要铅笔盒中的东西(带线索的抽象),但不能只说我要东西(不带线索的抽象)Why abstract class?UML: Abstract Factoryhttp://my.csdn.net/uploads/201205/27/1338112555_1716.png减小对象创建时,对特定实现类(构造函数)的依赖,如当创建 IValDial 时,必须使用 BBIValDial 或 LSIValDial 的构造函数当抽象类层次结构较复杂时,并且有从一个实现系统变为另一个实现系统(如从 BBWindow 变为 LSWindow)的预期时,需要一种一次性装入实现系统中各种创建对象的方法,这时抽象工厂就会发挥作用:
[*]创建具体工厂类对象
[*]将具体工厂类对象装入抽象工厂类对象(引用、指针)
[*]用抽象工厂类对象创建抽象构造块类
[*]使用抽象构造块类的方法,它动态绑定到具体构造块类的方法
1、2 是程序初始化阶段执行的设置,3、4 是程序例行阶段的行为于是,抽象工厂是和抽象类层次伴生的Clone 模式^UML: Clone Patternhttp://my.csdn.net/uploads/201205/27/1338112575_4317.pngWhy clone?手上有一个对象,只知道它的抽象类型(确切类型已丢失),要复制这种对象的大量副本,并且副本要和其确切类型一致可以定义一个 Clonable 抽象基类,以规约 clone 函数,但不是必须的Clone 模式可从函数 override 的返回值类型的 covariance 协变中受益。VC 2005+ 支持协变参考书籍^
[*] "The C++ Programming Language, Special Ed", Bjarne Stroustrup
[*] "Effective C++, 3Ed", Scott Meyers
[*] "C++ Primer, 3Ed", Stanley Lippman, Josee Lajoie
[*] "Inside The C++ Object Model", Stanley Lippman
页:
[1]