6 设计与面向对象设计
~~~~~~~~~~~~~~~~~~~~~
6.1 确定public继承表达的是is-a关系
===================================
1. 所谓is-a的关系不是指语言中的关系(比如鲸鱼是鱼,正方形是一种长方形等等),它要求能够施行于base class对象身上的每件事情,都可以施行于derived class对象身上.
2. 注意区分is-a(是一个),has-a(有一个),is-implemented-in-terms-of(根据某物实现出)
6.2 在子类中小心定义与父类相同名称的方法
=========================================
1. 子类中的同名方法会覆盖父类中的 *所有* 同名方法.即使父类和子类中的汗水有不同的参数类型也适用,不论函数是virtual或non-virtual方法都适用.
2. 可以在子类中用using 父类::同名方法的方式,使父类方法在子类中可见.而继承机制一如既往地运行
6.3 区分接口继承和实现继承
===========================
1. pure virtual函数也可以提供定义,但调用它的唯一方法是"调用时明确指出class名称":pt->class::pure_virtual_func()
2. 声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
3. 声明一个impure virtual函数的目的,是为了让derived class继承该函数的接口和缺省实现
可以通过将接口声明为一个pure virtual函数,将实现抽取作为一个non-virtual函数的方法来防止子类忘了重载impure-virtual的情况出现.但这种方法容易因过度雷同的函数名称而引起class命名空间污染问题.
我们也可以将接口声明为pure virtual函数,但同时也为这个pure-virtual提供缺省实现的方法来分离接口和缺省实现.
本质上,上面两种方法是为了将impure-virtual函数分割成两部分:声明部分表现的是接口(子类必须有),定义部分表现出缺省行为(子类可能有,但是需要在子类中明确调用),可以让着两个函数享有不同的保护权限.
4. 实现non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现.
6.4 考虑virtual函数以外的其他选择
==================================
6.4.1 NVI模式(non-virtual interface)
-------------------------------------
1. 令客户通过public non-virtual成员函数间接调用private virtual函数,称为NVI手法.
2. NVI手法允许derived class重新定义virtual函数,从而赋予它们"如何实现机制"的控制能力,但base class保留诉说"函数何时被调用"的权利.
3. NVI手法没必要一定让virtual是private,也可以是protected,但是不能为public
6.4.2 借由Function Pointers实现Strategy模式
--------------------------------------------
1. 通过在类中包含指向功能的指针来让类具有指定的功能
2. 这种方法可以实现
* 同一个类对象可以有不同的实现功能
* 实现功能可以在运行期变更
* 这种方法要求功能实现不依赖类的内部成分,因为这个方法已经不是类的成员函数了.
6.4.3 古典的Strategy模式
-------------------------
1. 传统的Strategy做法会将功能函数做成一个分离的类继承体系中的virtual成员函数.
6.5 绝不重新定义public继承而来的non-virtual函数
================================================
1. 若子类和父类有同名的non-virtual方法,那么一个指针调用哪个方法,就看指针声明类型是子类还是父类.
6.6 绝不重新定义继承而来的缺省参数值
=====================================
1. virtual函数是动态绑定的,而缺省参数值却是静态绑定的.
2. 指针的静态类型指声明该指针时的类型
指针的动态类型指指针所指对象的实际类型
3. 由于virtual函数是动态绑定,而却是函数是静态绑定的,所以当一个指向base饿指针在调用一个定义于derived class内的virtual函数时,使用的确实base class为它指定的缺省函数值!!
- struct B
- {
- virtual void say(string word = "b"){cout<<word<<endl;}
- };
- struct D:public B
- {
- virtual void say(string word = "d"){cout<<"i am "<<word<<endl;}
- };
- int main()
- {
- B* pb = new D();
- pb->say(); //i am b;
- return 0;
- }
4. 既然base中缺省参数和derived中的缺省参数必须一致,那么在derived中声明同样的缺省函数就显得代码重复,而且带着相依性了.
可以使用NVI的手法,让non-virtual函数指定缺省参数,而private virtual函数负责真正的工作.
6.7 明智而审慎地使用private继承
================================
1. 如果class之间的继承关系是private,编译器不会自动将一个derived class对象转换为base class对象.
由private base class 继承来的变量全部变为private的
2. private继承意味着implemented-in-terms-of(根据某物实现出).
如果你让class D以private形式继承class B,那么用意应该为采用class B内已经具备的某些特性,而不是因为B对象和D对象存在任何观念上的关系. private继承意味着只有实现部分被继承,而接口部分被略去
3. 复合的意义也是is-implemented-in-terms-of,而且复合比较容易理解,因此尽可能使用复合,必要时才使用private继承.
4. 当面对并不存在is-a关系的两个classes,其中一个需要访问另一个protected成员,或需要重新定义其一或多个virtual函数,则可以使用private继承.
6.8 明智而审慎地使用多重继承
=============================
1. 调用多个父类中同名函数的问题:
多个父类中的同名函数的调用是歧义的,即时两个函数中只有一个是可取的(即一个是public,一个是private).因为C++解析重载函数调用的规则是,先找出匹配函数,再检查其可取性.
为了解决这个歧义,必须明确指明调用哪一个base class中的函数,例如sub.base1::func()
2. 钻石型多重继承
所谓钻石型多重继承指多个父类继承于同一个base class.这是带来的问题是,这个base class中的成员变量,是否应该重复
C++缺省的做法是重复的,如果不想要重复,那么父类在继承base class时需声明为virtual继承,即class D:virtual public B
3. virtual继承
由于virtual继承的开销太大,因此
* 尽量不要使用virtual继承,多使用non-virtual继承
* 如果必须使用virtual继承,尽量避免在virtual-base-class中放置数据