第21章 类层次: UI 设计 & 不涉及 事件驱动技术
Note
(1)
————————————————————————————————————————————
用 `抽象类` 支持 `接口继承`
用 `带 vf实现 的 基类` 支持 `实现继承`
————————————————————————————————————————————
(2) 带限定符的调用 不会用到 虚调用机制
21.1 设计 类层次: 建立 "虚拟 UI 系统" - 分离 UI 与 应用程序
1 实现继承
(1) Base/Ival_box 类
1) 带状态: low/high/changed 等属性
2) 定义 成员 vf/non-vf
(2) Base/Ival_box 作类层次的 `接口类`
1) Derived 对象创建 函数
返回 智能指针 -> sp<Derived>.get() 取出 internal ptr
-> 隐转为 `Base/Ival_box 指针` 作 实参, 调 interact(Ival_box*)
2) 与 `user 程序` 的 `交互接口`
void interact(Ival_box* pb);
3) 类层次
BBwidget
|
| public 继承
|
Ival_box
box 形式 / \
/ \
/ \
滑块 旋钮
/ \
/ \ 响应 prompt() 的方式
/ \
出现在某处重要位置 闪烁
(3) 4 个问题
2) Ival_box data 是 实现细节, 却混入 Ival_box 接口
2) UI 系统(BBwidget) 是 实现细节 ( BBwidget ), 却被拔高到 `高层策略`
3) UI 系统改动 会导致 `user 代码 重新编译`
4) 不同 UI 系统 ( BBwidget / CWwidget ) 不能被同时支持
`隔离` BBwidget( CWwidget ) 与 Ival_box: 放 `同一层次`
2 接口继承
(1) Ival_box 作 纯粹接口:
不带状态 (数据) & 不定义 vf , 不含 nvf
user 程序 与 接口类 Ival_box 间 交互代码, `不因` Ival_box 派生类 Ival_slider 的 `改变 而变`
(2) Ival_slider
1) public 继承 自 Ival_box (接口类)
2) protected 继承
自 UI 系统/BBwidget (辅助实现类)
——————————————————————————————————————————————————————————————————————————————————
BBwidget -> Ival_slider 如何选 `继承方式` ? public / protected / private ?
——————————————————————————————————————————————————————————————————————————————————
[1] private 继承
1] 阻止进一步派生
派生类 Ival_slider 的 派生类 无法访问 BBwidget
2] 含义: is-implemented-in-terms-of ( 据某物实现出 )
——————————————————————————————————————————————————————————————————————————————————
2] public 继承
is-a -> 含义不对
`实现类 BBwidget` 提供 `public 的 实现细节` -> 误用
——————————————————————————————————————————————————————————————————————————————————
[3] protected 继承
1] 可进一步派生: `派生类 Ival_slider` 的 `派生类 可访问 BBwidget`
2] 含义: is-implemented-in-terms-of ( 据某物实现出 )
——————————————————————————————————————————————————————————————————————————————————
=> protected 继承 是最好方式
(3) 多重继承: 实现类 ( BBwidget )
——————————————————————————————————————————————————————————————————————————————————
作 `接口类 Ival_box` 的
——————————————————————————————————————————————————————————————————————————————————
[1] 基类
依赖
——————————————————————————————————————————————————————————————————————————————————
[2] 成员
`无法覆盖` Ival_box vf
——————————————————————————————————————————————————————————————————————————————————
[3] 指针成员: BBwidget*
`额外 forward( 转发 ) 开销`
——————————————————————————————————————————————————————————————————————————————————
[4] `平行类: Ival_box 派生类 Ival_slider 的 protected 基类`
——————————————————————————————————————————————————————————————————————————————————
=> 作 `接口类 Ival_box` 的平行类最好
3 灵活变形
(1) `接口类 Ival_box` 派生出 `抽象的派生类`, 与 实现类 BBwidget 同层
(2) 实现类 层次中添加 专有类: 优化`
4 对象创建: Factory - 虚 createFunc
// 1] 抽象 factory 类
class Ival_maker
{
public:
virtual Ival_dial* dial(int, int) = 0;
// ...
};
// 2] 派生
class BB_maker: public Ival_maker
{
Ival_dial* dial(int, int) override
{
return new BB_ival_dial(a, b);
}
};
// 3] 用户
void user(Ival_maker& im)
{
unique_ptr<Ival_maker> pb { im.dial(0, 99) };
// ...
}
// 4] Factory 对象
BB_maker BB_impl;
// 5] `驱动程序`
void driver()
{
user(BB_impl); // 使用 BB
}
21.2 多重继承
1 多重接口类 = 多重 抽象类
作 接口
+ 不含 可变状态
可被 `复制/copy` 和 `共享`
2 多重实现类
(1) Derived* 作实参调 paraType 为 Base1* 的 func, func 中 只能看到 DerivedObj 的 Base1 part
(2) 类 B1/B2 为什么要作 基类
, 而不是用 成员(B1 b1/B2 b2) 或 指针成员(B1* / B2*)?
答: D 想 override B1/B2 各自的 vf
B1 B2
|\ /|
\ /
\ /
D
void f1(B1*);
void f2(B2*);
void f(D* pD)
{
f1(pD);
f2(pD);
}
(3) 意义
————————————————————————————————————————————————————
优点
`无 额外 forward( 转发 ) 开销`
————————————————————————————————————————————————————
折中
`不影响 总体设计`
————————————————————————————————————————————————————
缺点
有时会不小心暴露 实现细节
but, 总体上看, 没有任何技术 能够做到足够完美
————————————————————————————————————————————————————
3 二义性 解析
(1) `2个 基类 memFunc 同名`
|
| 解决1
|/
显式限定 消除
|
| 解决 2: 中间层/封装
|/
`直接` 在 `多重派生类` 中 定义 `同名新函数`: `hide` 基类中 所有该 `同名 实体`
|
| 其中
|/
显式限定 消除
(2) 中间层 BB1/BB2 对 B1/B2
的 NVI (non-virtual interface ) 手法
的 Template Method
1] BB1/BB2 把 B1/B2 的功能映射下来
2] using 声明 继承 Ctor
3] non-vf
调 纯虚 vf
-> 延迟 redefined/override 于 D 中
#include <iostream>
struct B1
{
void f() {};
};
struct B2
{
void f() {};
};
struct BB1 : B1
{
// BB1 把 B1 功能映射下来
public:
using B1::B1;
virtual void b1_vf() = 0;
void f()
{
b1_vf(); // <=> this->b1_vf(): 多态
}
};
struct BB2 : B2
{
// BB2 把 B2 功能映射下来
public:
using B2::B2;
virtual void b2_vf() = 0;
void f() { b2_vf(); }
};
class D : public BB1, public BB2
{
public:
virtual void b1_vf() { std::cout << "B1: f()\n"; };
virtual void b2_vf() { std::cout << "B2: f()\n"; }
};
int main()
{
D d;
BB1* p = &d;
p->f();
BB2* p2 = &d;
p2->f();
}
(3) 其中之一用 const: 重载机制区分
4 重复使用 基类: 菱形继承
1 个 `最远派生类 对象` 包含 `2 个 最远 Base 对象`
|
| 问题
|/
若 最远 Base 含 data, 如何 确保 data 不被 `重复使用`
| |
| | 2个问题
| |/
| 在 最远派生对象 中 `占内存` + `二义性`
|
| 解决
|/
`基类` 声明为 `virtual`: 避免重复
|
|/
5 虚基类
(1) 虚基类 包含 数据:
想 共享 数据
————————————————————————————————————————————————————————
`类层次` 中 `2个类 共享数据` 的 3 种 明显方式
————————————————————————————————————————————————————————
[1] data 放 `non-local ( global / namespace ) scope`
破坏 封装性 和 局部性
————————————————————————————————————————————————————————
[2] data 放 `基类`
单继承: data "冒泡" 到 `public 继承树 的 根部`
与 non-local 问题一样
————————————————————————————————————————————————————————
[3] 某处 `分配 对象`, `指针` 分别 `交给 2 个类`
`通过指针(偏移量) 共享数据: 虚继承`
————————————————————————————————————————————————————————
(2) 虚基类 对象内存布局 : 虚继承机制
Derived 对象
中 的 指针/偏移量
: "指向" 共享对象
(3) 虚基类 Ctor/Dtor
1) 虚基类 Ctor:
由 最终派生类 的 Ctor
负责 (显式 或 隐式) 调用
=> `确保只会被 调用 1次` (即使被 提及多次)
即 `虚基类` 永远被认为是其 `最终派生类 的 直接基类`
2) 虚基类 Dtor: 无 Ctor 那样的问题
6 重复基类 与 虚基类
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(1) 用 `抽象类` 作 `接口` | |
| 实现 | note
可选择 | |
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
[1] `重复 接口类` | public 最远基类 和 public 中间派生类 | `最远派生类指针 不能隐式转换` 为 最远基类指针
| 均 不设为 virtual |
类层次 中 `每提到1次 创建 1个对象` | |
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
[2] `接口类` 设为 `virtual` | `public 最远基类` 设为 virtual | `基类 向 派生类` 的 `强转类型转换` (22.1 节) 产生 二义性 -> 易处理
| |
类层次 中 `所有提及` 它的类 `共享1个对象` | |
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(2) 覆盖 虚基类 vf
D11/D12 均 public virtual 继承 B, 且都 override 了 B 中的 vf, 则 D2 中也必须 override vf, 否则 完整对象 D 上调用该 vf 具有二义性, 无法构建 vtbl
B
/ \
/ \
D11 D12
\ /
\ /
D2
实现继承: Ival_box 从哪获取 图形元素? 通过 继承自 UI 系统类( BBwidget ) .jpg
接口继承 + 实现继承.jpg
接口继承 + 实现继承: 4 种版本