十一 组合和继承
OOD(Object Oriented Design)
(1)基于对象:单一类的写法;
(2)面向对象:类与类之间的关系,包含三种:Composition(复合)、Delegation(委托)、 Inheritance(继承)。
11.1 Composition表示has-a
如上图所示,queue类中有deque类,称为queue has-a deque即类的复合。
这也是一种设计模式Adapter,queue类拥有deque类,而且queue所有的函数功能都借用deque类来实现。deque功能非常全,queue根据用户开放部分的功能,queue是一种adapter。
这是复合在内存中的表现,queue类有deque类,deque类有Itr类,sizeof的计算如图所示:
类之间在复合关系下构造函数与析构函数的顺序为:
(1)构造由内而外。container的构造函数首先调用Component的default构造函数,然后才执行自己。
Container::Container(...):Component( ){ ... };
(2) 析构由外而内。container的析构函数首先执行自己,然后才调用component的析构函数。
Container::~Container(...){ ... ~Component( ) };
11.2 Delegation(委托) (Composition by reference)
(1)如图所示左边有指向右边的指针,这种叫委托。它和复合的区别是复合内外部是一起出现,两者生命周期同步;而委托则是允许外部先创建出来,等需要的时候再把内部写好,两者生命周期不同步。
(2) 图示写法叫做Handle/Body或者pointer implementation(pimpl)。左边是对外的接口,右边是功能的实现,右边不影响左边,右边可以更改甚至指向别的类,具有良好的弹性。
11.3 Inheritance(继承)表示is-a
子类从父类继承了数据和函数(函数继承的是调用权)。
类之间在继承关系下构造函数与析构函数的顺序为:
(1)构造由内而外。Derived的构造函数首先调用Base的default构造函数,然后才执行自己。
Derived::Derived(...):Base( ){ ... };
(2) 析构由外而内。Derived的析构函数首先执行自己,然后才调用Base的析构函数。Base class的析构必须是virtual,否则会出现undefined behavior!
Derived::~Derived(...){ ... ~Base( ) };
十二 虚函数与多态
12.1 虚函数及其应用
继承最有价值的是和虚函数的搭配使用。成员函数从虚函数的角度出发分为三种:
(1)non-virtual函数:你不希望derived class重新定义(override,重写)它;
(2)virtual函数:你希望derived class重新定义(override,重写)它,且你对它已有默认定义;
(3)pure virtual函数:你希望derived class一定要重新定义(override,重写)它,且你对它没有默认定义。
虚函数应用实例如下:
旧文件的开启与读取,check file name 、search file 、open file任何人写的都差不多,可以事先写,但是因为每个人读取的文件类型不一样,所以读取这个动作不能事先写。框架如下:
应用框架CDocument中已经事先写好check file name 、search file等函数,CMyDoc是CDocument的子类,没法事先写的Serialize()设计为虚函数(可能为空函数,也可能为纯虚函数),使用流程如图箭头所示,创建一个子类的对象,通过子类的对象调用父类的函数,遇到Serialize()时父类函数去找子类的定义,然后再将剩余的动作完成,将Serialize()延缓到子类去定义,这种用法就叫做Template Method(23种设计模式之一,Method是java中的函数)。
这种设计模式适合做框架,MFC大量用到Template Method。
具体代码实现(仿真):
12.2 继承+复合关系下的构造与析构
在图示继承+复合关系下构造函数与析构函数的顺序为:
(1)构造由内而外。Derived的构造函数首先调用Base的default构造函数,然后调用Component的构造函数,最后才执行自己。
Derived::Derived(...):Base( ),Component(){ ... };
(2) 析构由外而内。Derived的析构函数首先执行自己,然后调用Component的析构函数,最后调用Base的析构函数。
Derived::~Derived(...){ ... ~Component(),~Base( ) };
在图示继承+复合关系下构造函数与析构函数的顺序为:
(1)构造由内而外。Derived的构造函数首先调用Component的default构造函数,然后调用Base的构造函数,最后才执行自己。
Derived::Derived(...):Component(),Base( ){ ... };
(2) 析构由外而内。Derived的析构函数首先执行自己,然后调用Base的析构函数,最后调用Component的的析构函数。
Derived::~Derived(...){ ... ~Base( ) ,~Component()};
12.3 多态实例
功能最强大的是委托+继承设计方式。
委托+继承设计实例1(Observer):
左边为放数据的class,右边为观察的class,左边可以有很多的右边,左边数据装有指向右边指针的容器,右边可以被继承,将来创建的子类is a Observer都可以放在容器里面,所以可以产生不同的Observer。
左边要提供注册和注销的功能,如图attach传入Observer放到容器里头。左边还应该有notify把所有Observer进行遍历,去通知Observer,内容由Observer写好,左边调用。
如图所示有多个窗口看同一份文件或者不同角度看同一份数据。实现代码如下:
委托+继承设计实例2(Composite):
文件系统:Primitive代表文件,Composite是一个容器,容纳很多个Primitive和Composite,所以设计一个父类Component,Primitive和Composite都is a Component,Component和Composite是委托的关系。add函数和容器类似,add不能设计为纯虚函数,因为Primitive不能定义。
委托+继承设计实例3(Prototype):
我需要一个树状继承体系,子类未来才被派生,不知道未来子类的名称。让派生的子类创建一个自己当成原型Prototype,让我有办法去看到子类创建出来的的原型放在什么位置上。
在LandSatImage类里面放一个静态的对象LAST,他的类型是LandSatImage(自己),构造函数写成私有的,通过私有的构造函数调用addPrototype,将自己挂上去,addPrototype是父类写的,它将得到的指针放到容器里头去,这样就可以使破折号下面创建的原型放到上面去,可以被上面看到。子类准备一个函数clone,它new一个自己。破折号以上可以通过原型(这是一个对象)可以调用clone这个函数,做出一个副本,如果没有原型则不能。
clone不能是静态函数,因为静态函数的调用需要class name,这里没有。
实现代码如下: