第十节 组合与继承
基于对象的三种常见关系:
- 继承(Inheritance)
- 复合(Composition)
- 委托(Delegation)
Composition(复合)
Adapter模式
以Adapter(改造)模式为例。
template<class T>
class queue
{
...
protected:
deque<T> c; //底层容器
public:
//一下完全利用c的操作函数完成
bool empty()const{return c.empty();}
size_type size()const{return c.size();}
reference front(){return c.front();}
reference back(){return c.back();}
//
void push(const value_type& x){c.push_back(x);}
void pop(){c.pop_front();}
};
组合关系图:
内存分布:
sizeof:40
template<class T>
class queue
{
protected:
deque<T> c;//40----->>
...
};
sizeof:16*2+4+4
template<class T>
class deque
{
protected:
Itr<T> start;//16----->>
Itr<T> finish;//16
T** map;//4
unsigned int map_size;//4
};
sizeof:4*4
template<class T>
struct Itr
{
T* cur;//4
T* first;//4
T* last;//4
T** node;//4
}
复合关系下的构造和析构
以Container和Component为例:
- 构造由内而外:Container::Container(...):Component(){...},先调用内部构造,再去做自己的事情。
- 析构由外而内:Container::Container(...):{...Component"()},先把自己的事情做完,再调用内部析构。
Delegation(委托)
Delegation:Composition by reference.
即用指针实现组合。
Handle/Body(pImpl)模式:
class String
{
public:
String();
String(const char* s);
String(const String& s);
String &operator=(const String& s);
~String();
private:
StringRep* rep;//pimpl
};
#include"String.hpp"
namespace
{
class StringRep
{
friend class String;
StringRep(const char* s);
~StringRep();
int count;//reference counting
char* rep;//指向字符串
};
String::String(){...}
}
委托关系图:
String类提供对外接口,而内部StringRep提供具体实现,与组合用法类似,不同点是委托中两者通过指针来连接,其中一点好处是只有在使用时才会为右侧分配内存,节省资源。
Handle/Body模式也叫编译防火墙,使代码更有弹性,右侧无论如何更改不会影响左侧,更不会影响客户端,实现了独立。
reference counting:统计当前有多少份String指向StringRep(共享rep字符串)。
copy on write:当其中一份String想修改rep时,为避免影响其他String读取原rep,为其copy一份供其更改。
Inheritance(继承)
示例代码:
struct _List_node_base
{
_List_node_base* _N_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node
:public _List_node_base
{
_Tp _M_data;
};
继承关系图:
常用的继承是公有继承(public),在公有继承中基类的私有在子类中不可访问,基类中的保护在子类中仍然是保护。因此,当我们想把数据封装在当前类中不希望任何人(包括子类)访问时可以将它设为私有,如果希望在子类中处理则设为保护。
继承关系下的构造和析构
Derived作为Base的子类,包含Base,内存与代码两个角度都是这样。视Base为内部,Derived为外部。其构造与析构有以下关系:
- 构造由内而外:先调用内部的构造函数,再调用外部。
- 析构由外而内:先调用外部的构造函数,再调用内部。
示例如下:
另外,基类的析构必须是virtual,否则会出现undefined behavior的错误。
第十一节 虚函数与多态
Inheritance with virtual functions
- non-virtual函数:不希望derived class 重新定义(override)它。
- virtual函数:希望derived class重新定义,并且已经有默认定义。
- pure virtual函数:希望derived class一定要重新定义,且对它没有默认定义。
Template Method模式
(MFC使用该模式)
//Application framework
CDocument::OnFileOpen()
{
...
Serialize()
...
}
//该函数设计了一系列动作(用...表示),
//其中一个动作(Serialize())现在无法决定,
//可能要退后几年去实现,
//则将他设计为虚函数。
class CMyDoc:public CDocument
{
virtual Serialize(){...}
};
main()
{
CMyDoc myDoc;
...
myDoc.OnFileOpen();
//编译器转化为
//CDocument::OnFileOpen(&myDoc)
//&myDoc作为this指针传入函数
}
解析:在主函数中执行myDoc.OnFileOpen(),并将&myDoc作为this指针传入基类的OnFileOpen()函数,当遇到虚函数时,根据this指针跳转到相对应的子类中执行相对应的函数。
Template Method模式实例
#include<iostream>
using namespace std;
class CDocument
{
public:
void OnFileOpen()
{
//这是个算法,每个cout输出代表一个实际动作
cout<<"dialog..."<<endl;
cout<<"check file status..."<<endl;
cout<<"open file..."<<endl;
Serialize();
cout<<"close file..."<<endl;
cout<<"updata all views..."<<endl;
}
virtual void Serialize(){};//可以定义为纯虚函数
};
class CMyDoc:public CDocument
{
public:
virtual void Serialize()
{
//只有应用程序本身才知道如何读取自己的文件(格式)
cout<<"CMyDocheritance+Composition::Serialize()"<<endl;
}
};
in main()
{
CMyDoc myDoc;//假设对应[File/Open]
myDoc.OnFileOpen();
}
Inheritance+Composition关系下的构造和析构
Derived包含其他两类,所以Derived为外部,Base,Component为内部。
Component为内部,Base为Component的外部,Derived为Base的外部。
- 构造由内而外
- 析构由外而内
Inheritance+Delegation实例
Observer观察者模式
class Subject//内容物
{
int m_value;
vector<Observer*>m_views;
public:
void attach(Observer* obs)//注册观察权限,将观察方式传入
{
m_views.push_back(obs);
}
void set_val(int value)
{
m_value=value;
notify();
}
void notify()//便利更新数据
{
for(int i=0;i<m_views.size();++i)
m_views[i]->updata(this,m_value);
}
};
class Observer
{
public:
virtual void update()Subject* sub,int value)=0;
};
Subject与Observer是委托的关系,Observer与其子类是继承的关系。
这个例子也是听得一头雾水,慢慢消化吧。
今天就到这里,晚安。
第十二节 委托相关设计
Composite模式
以文件系统为例。
class Component//基类
{
int value;
public:
Component(int val){value=val;}
virtual void add(Component*){}
};
class Primitive:public Component//文件(基本单位)
{
public:
Primitive(int val):Component(val){}
};
class Composite:public Component//目录
{
vector<Component*> c;
public:
Composite(int val):Component(val){}
void add(Comonent* elem)
{
c.push_back(elem);
}
};
解析:Component类表示目录类,Primitive表示文件类,目录中既可以包含文件,也可以包含目录自身,但目录类中的向量只能存储一种(相同的)数据类型,这时我们可以通过为文件类与目录类建立共同的基类来联系二者,并在目录类中的向量中保存指向基类的指针。
另外,关于add函数,因为需要在目录类中重新定义,所以在基类中声明为虚函数,在文件类中定义空即可(因为文件不能再添加文件),在目录类中重写。如果设计为纯虚函数则在文件类中一定要重写该函数,但在实际操作中由不可以进行该操作(添加文件),这种设计是不合理的。
Prototype 模式
该例难度系数较大。
#include<iostream>
enum imageType
{
LSAT,SPOT
}
class Image
{
public:
virtual void draw()=0;
static Imag* findAndClone(imageType);//为数组中所有指针分配内存(只是举例,特殊情况特殊考虑)
protected:
virtual imageType returnType()=0;
virtual Image* clone()=0;//向子类提供分配内存接口
static void addPrototype(Image* image)//用于接受扩展类返回的指针
{
_prototypes[_nextSlot++]=images;
}
private:
static Image* _prototypes[10];//存储扩展类指针
static int _nextSlot;
};
Image* Image::_prototypes[];
int Image::_nextSlot;
Image* Image::findAndClone(imageType type)
{
for(int i=0;i<_nextSlot;i++)
{
if(_prototypes[i]->returnType()==type)
return _prototypes[i]->clone();
}
}
class LandSatImage:public Image//与之后SpotImage同属扩展类,只分析此例
{
public:
imageType returnType()
{
return LSAT;
}
void draw()
{
cout<<"LandSatImage::draw"<<_id<<endl;
}
Image* clone()//分配内存(通过调用有参函数),通过在父类调用数组中相对应的指针
{
return new LandSatImage(1);
}
protected:
LandSatImage(int dummy)
{
_id=_count++;
}
private:
static LandSatImage _landSatImage;//定义自身,调用无参构造,返回指针给父类
LandSatImage()//无参构造,向父类返回指针
{
addPrototype(this);
}
int _id;
static int _count;
};
LandSatImage LandSatImage::_landSatImage;
int LandSatImage::_count=1;
class SpotImage:public Image
{
public:
imageType returnType()
{
return SPOT;
}
void draw()
{
cout<<"SpotImage::draw"<<_id<<endl;
}
Image* clone()
{
return new SpotImage(1);
}
protected:
SpotImage(int dummy)
{
_id=_count++;
}
pirvate:
SpotImage()
{
addPrototype(this);
}
static SpotImage _spotImage;
int _id;
static int _count;
};
SpotImage SpotImage::_spotImage;
int SpotImage::_count=1;
解析:例子很长,我们只分析其中的精华部分。prototype(原型)模式的特点是可以在项目初期只设计一个原型,而在之后可以根据客户的不同需求扩展各种不同的class。那么它的实现
原理是怎样的呢?
首相创建一个原型类,在类中通过纯虚函数声明之后的扩展类需要提供的接口,并定义一个原型类(父类)指针的数组,用来存储各种扩展类(子类)的指针。
之后我们来设计扩展类(anytime),首先我们要在其私有成员中声明一个静态的自身,其会调用到无参的构造函数,会返回一个指向自身的指针给父类,父类接受到指针后存进数组,这样父类就起到了一个监视的作用,可以动态的收集子类指针。但是这里我们只是返回了一个指针,并不算是一个真正的数据,因为没有内存块,当我们真正需要使用的时候只需要在父类的数组中找到相对应的指针,为其分配内存即可(通过调用有参构造)。
学习完本课程深深地体会到:第一批搞设计模式的人绝对都是世界上最聪明的那类人。