0%

4.1 面向对象

– 杂谈而已,徒然博君一笑

首先要明确的是,就能力来讲,面向对象不比面向过程更强大。新的编程模式只是选择用 一丢丢性能 或者其他换取 易扩展性、健壮性等 东西

试想我们是从面向过程向面向对象转变的程序🦍

面向对象三大特征

  • 封装
  • 继承
  • 多态

封装

封装的意义在于 为具有一定关系的数据集合命名
于是相对于无明确相关意义的零散变量对象 作为一种新的逻辑实体 出现在我们的视野里
后边的所有东西都是围绕着 这俩货 的探讨。

PS:纯粹的封装是没有开销的。所谓封装事实上只是提供了一种 聚合数据的 作用。在引入 其他的需要运行时的时间空间开销的概念进来之前,封装只是最基本的操作。面向过程的C也还有struct呢,c只是对继承和多态不感兴趣罢了。

继承

继承分为两种,接口继承与实现继承。
继承的意义在于 代码复用,但接口继承的代码复用和实现继承的代码复用差距甚远。接口继承允许 接口的使用代码 被复用;实现继承 允许父类的代码被复用。两者可以兼容,但两者兼具时就会造成一定意义上的混淆。
就C++的成员函数而言, 基础普通成员函数就是实现继承,虚函数就是 接口继承+实现继承

实现继承

试想:当只有纯粹的 实现继承发生时,类层次之间的关系更多表现为一种 静态的组合:等于是子类拥有一个父类的实例成员。但是用户还是得把两者的代码区分开

实现继承的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 struct Base{
void what(){};
};
struct Derived:public Base{
void what(){
Base::what();
...;
};
}
int main(){
// Base
Base b;
b.what();

// Der
Derived d;
d.what();
d.Base::what();
}

在用户看来,实现继承在使用上没有任何帮助–不能减少任何代码。(不考虑模版元编程这种只凭名字就能做到复用客户代码的技术)。
所以 实现继承是一种 模块内的技术,只是发生在子类里的代码复用而已。
另一方面,实现继承是 对类相关关系的归纳,是类间 共性的表示,对于厘清类层级有很大帮助。

接口继承

接口继承允许客户代码 复用。

  • 核心:父类接口调用代码的延迟绑定
  • 表现: 子类实例可以平替接口对象

看一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct VBase{
virtual void what(){};
};
struct VDerived:public VBase{
virtual void what()override{
VBase::what();
...
}
}

void doWhat(VBase & base){
base.what();
}
int main(){
VBase b;
vDerived d;

doWhat(b);
doWhat(d);
}

VDerived::what() 中 使用了 继承来的 VBase::what(), 这只是举例说明 虚函数的实现也会被继承,实际上更多时候整个接口实现都会被重写,而与基类接口实现无关。

注意:doWhat 就是客户代码,接口继承允许 客户代码重用。

大型系统的开发,可以按是否依赖其他模块,区分为两部分:

  1. 未依赖其他模块的代码可以看作纯粹的子模块,这一部分代码不涉及系统的子模块之间的依赖,考量时的上下文可以缩减到子模块本身,于是复杂度降低。
  2. 涉及到子模块交互的代码。子模块之间的依赖的存在,就意味着客户的存在,由于子模块之间的复杂性(双向依赖),客户之间的关系错综复杂. 减少这类依赖关系带来的开发复杂性,就是接口继承的意义。 所以有种约束叫做:“规定子模块之间只能通过接口交互”

多态

泛泛而论,多态指的是 代码运行时的多种可能,对C++而言,多态就是 延迟绑定使能的 接口的运行时查表执行。
多态是与接口继承息息相关的。