1、三种关系
1.1 Composition 复合 (has a)
1.1.1 定义
表示“has a ”,即“拥有”关系。一个class里面,有另一种class的东西。即我这种类包含有另外一种类的对象。
1.1.2 好处
所有的功能不用自己写,直接调用他拥有类的功能就好。如下例queue中使用deque的对象,即为复合关系,就可直接调用deque的成员函数。下例可见deque是很强大的
例:
template <class T, class Sequence = deque<T> >
class queue
{
protected:
Sequence 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(); }
//deque是两端可进出,queue是末端进前端出
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
同样的,简单来看:
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(); }
//deque是两端可进出,queue是末端进前端出
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
用图来表示:
queue类里拥有deque的东西,则在queue类用实心菱形表示,指向它含有的deque
1.1.3 引入一种设计模式:Adapter
一种的功能可以完全满足另一种需要的功能,如deque能完全满足queue。则只需改一些deque的接口即可,如上代码
1.1.4 queue内存结构
构造由内而外:要构造外面的东西,需要先把内部的东西构造完成。即调用内部的默认(default)构造函数,然后才执行自己。
析构由外而内:要进行析构,先把外面的东西剥离掉,即先执行自己,然后才调用内部的析构函数。
1.2 Delegation 委托 (Composition by reference)
(虽然是用指针在传,但术语都叫做by reference)
1.2.1 定义
不同于上面复合的“拥有”,是实实在在的包含,委托是一种用指针来指向另一个类的方式。在任何我想用你来做事情的时候,就可以用指针调用你,把任务委托给你。所以委托可以看做是一种比较虚的拥有,因此在图中用虚的菱形。
//String.hpp,这里以后科研不用再改变
class StringRep;
class String
{
public:
String();
String(const char* s);
String(const String& s); //拷贝构造
String &operator=(const String& s); //拷贝赋值
~String(); //析构
private:
StringRep* rep //pimpl
};
//String.cpp,要改变操作,改变这里的即可。下图的圈n就是这里的操作
include"String.hpp"
namespace
{
class StringRep
{
friend class String;
StringRep(const char* s)
~StringRep();
int count; //后面的n
char* rep; //后面的rep
};
}
String::String(){......}
以上结构就是pimpl:pointer to implementation,也称Handle/Body。即左边String类都是借口,而下边StringRep都是实现,无论body是如何实现的,都不会影响handle。
1.2.2 与Composition的不同之处
Composition是有了外面就要有里面。(同步)
Delegation是有了外面不一定有里面,当外面调用里面时,才会创建里面的内容。(不同步)
1.2.3 不可以牵一发而动全身
当a要对hello进行改变时,b和c不能改变。因此应当在a改变之前进行复制,给a一个副本让a进行改动,b和c不变。这就是copy on write(在写的时候进行复制)。
1.3 Inheritance 继承(is a)
1.3.1 定义
写法:要定义一个类时,用冒号,后面加上继承方式,最后加上要继承的类。
语法:从子类往父类画,一个空心的三角形。(子类的东西比父类多)
1.3.2 继承方式
public:使用public继承,传达出一种逻辑:是一种,即“is a”。这种继承方式是最重要的,后面两种其他语言不一定有,都默认public继承。
还要其余两种继承方式:private和protected,这里请先查阅相关书籍。
1.3.3 内存
子类的对象中要有父类的成分(part),所以这也是一种外部包含内部的状态,如Composition 。
因此构造由内而外:要构造外面的东西,需要先把内部的东西构造完成。即调用Based的默认(default)构造函数,然后才执行Derived的。析构由外而内:要进行析构,先把外面的东西剥离掉,即先执行Derived的,然后才调用Based的析构函数。
注:父类Based的dtor必须是virtual的!
1.3.4 好处——virtual
子类除了拥有自己的数据和方法以外,还可以拥有父类的。但最有价值的是和虚函数搭配的情况。
继承的过程:数据可以被继承下来,函数也可以被继承下来(继承的是调用权),根据子类是否重新定义决定是否virtual。
class Shape
{
public:
int objectID() const; //non virtual
virtual void error(const std::string& msg) //virtual
virtual void draw() const = 0; //pure virtual
};
class Rectangle : public Shape{...};
class Ellipse : public Shape{...};
非虚函数non virtual:不希望子类重新定义(也叫重写override,只有继承的时候可以用这个词);
虚函数virtual:已经有了默认定义,希望子类重新定义;
纯虚函数pure virtual:希望子类一定重新定义;
1.3.5 经典用例——Template Method:框架设计模式
方法可以跑向子类:
可以跑去的原因:this指向的是myDoc,通过子类this调用,见下图左上部分。
1.main函数创建子类对象myDoc
2.通过子类对象调用父类函数OnFileOpen
3.父类函数Serialize看到子类有写这个函数。执行的过程,通过子类的virtual定义的方法执行子类的方法,再回到父类继续的方法。
大体代码如下:
2、三种关系的组合
2.1 继承+复合
构造和析构的顺序:把握住一点
构造:由内而外
析构:由外而内
子类是比父类要大一圈的,在外面;复合关系是类“拥有”的,在里面。
2.2 委托+继承
2.2.1 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]->update(this, m_value);
}
};
class Observer
{
public:
virtual void update(Subject* sub, int value) = 0;
};
模式类型见上图的右上角。
Observer是指向右边的指针(委托),用于观察,可以被继承成不同的观察方式。而m_view是观察方式对象。
set_val函数作用就是放数据。
attach函数作用是注册,把观察方式对象放进容器。
notify函数作用是遍历、通知,准备更新(子类update)数据。
2.2.2 Composite设计模式
类似目录,可以有同级目录,可以有下一级目录:
class Primitive:public Component
{
public:
Primitive(int val):Component(val) {}
};
class Component
{
int value;
public:
Component(int val) { value = val; }
virtual void add(Component*) {}
};
class Composite:public Component
{
vector<Component*>c;
public:
Composite(int val):Component(val) {}
void add(Component* elem)
{
c.push_back(elem);
}
...
};
Primitive:个体
Composite:组合物。应该可以容纳很多个Primitive,甚至可以容纳自己这种东西。所以应该继承Component
Component:add操作可以加左边的东西,也可以加右边的东西。故传入一个指针指向Component类型。add不能是纯虚函数,故应该是空函数。
2.2.3 Prototype设计模式
一个继承体系,想去创建未来才会出现的子类。即现在要去创建未来的class对象!
上图中,红色线以上的部分是父类(多年前写的部分),而下面的子类是未来(买代码的 买回去后才自己定义的)才会被派生出去的。
解决方案:让子类都创建一个自己,当成“原型”,让父类有办法去看到子类创造出的原型放在什么位置上。这样父类就可以复制他,就等于父类在创建了。
有下划线的部分是指静态的,LAST即为静态对象,其类型名即LandSatImage。
为了让父类看到,定义了父类。构造函数LandSatImage()是private的,调用父类的addPrototype函数,把得到的子类指针放到父类prototypes容器中去;LandSatImage(int)是protected的
clone函数就是new自己,做一个副本出来,如果没有这个原型,就无法通过对象调用函数(若要用静态函数不用原型,就需要class name,而父类并不知道)。
#include<iostream.h>
enum imageType
{
LSAT, SPOT
};
class Image
{
public:
virtual void draw() = 0;
static Image *findAndClone(imageType);
protected:
virtual imageType returnType() = 0;
virtual Image* clone() = 0;
//As each subclass of Image is declared, it registers its prototype
static void addPrototype(Image *image)
{
_prototypes[_nextSlot++] = image;
}
private:
//addPrototype() saves each registered prototype here
static Image* _prototypes[10];
static int _nextSlot;
};
Image *Image::prototypes[];//定义
int Image::_nextSlot;//定义
//Client calls this public static member function when it needs an instance
Image *Image::findAndClone(imageType type)
{
for(int i = 0; i < _nextSlot; i++)
{
if(_prototypes[i]->returnType())
return _prototypes[i]->clone();
}
}
class LandSatImage:public Image
{
public:
imageType returnType()
{
return LSAT;
}
void draw()
{
cout << "LandSatImage::draw" << _id << endl;
}
//When clone() is called, call the one-argument with a dummy arg
Image *clone()
{
return new LandSatImage(1);
}
protected:
//This is only called from clone()
LandSatImage(int dummy)
{
_id = _count++;
}
private:
//Mechanism for initializing an Image subclass - this causes
the default ctor to be called, which registers the subclass's prototype
static LandSatImage _landSatImage;
//This is only called when the private static data member is inited
LandSatImage()
{
addPrototype(this);
}
//Nominal "state" per instance mechanism
int _id;
static int _count;
};
//Register the subclass's prototype
LandSatImage LandSatImage::_landSatImage;
//Initialize the "state" per instance mechanism
int LandSatImage::_count = 1;