类与类之间的关系

  • Object Oriented Programming OOP
  • Object Oriented Design OOD
  • 类和类之间的关系
    • 复合composition
    • 继承inheritance
    • 委托delegation

复合表示has-a

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(); }
	//
	void push(const value_type& x){ c.push_back(x); }
	void pop() { c.pop_front(); }
  • 一个结构中有另一种结构,叫做复合。上例中queue中有deque< T>
  • 设计模式Adapter:适配,一个已有的东西已经可以完全满足客户的需求,但是接口可能不一样,将它改造以适配。
  • 两个类的生命周期是同步的
  • 顺序
    • 构造由内而外,外层的构造函数先调用内层元素的default构造函数,再构建外层元素
    • 析构是由外而内,外部的析构函数先执行自己,再调用内部的析构函数
    • 次序是编译器自主实现,类只需写自己的构造函数

委托表示一个类内的数据有另一个类的指针Composition by reference

  • 两个类的生命周期不同步,一个类提供对外的接口,另一个类负则具体的实现。负则接口的类会先被创建,当需要另一个类的对象时才创建它。
  • 一个类提供对外的接口,具体实现都通过调用另一个类的函数来实现(Handle / Body)
  • 优点:handle中的指针可以指像不同的body,即同一个接口,可以提供不同版本的实现类。
  • 也称为编译防火墙
//file String.hpp
//Handle
class StringRep;
class String{
public:
	String();
	String(const char* s);
	String(const String& s);
	String &operator= (const String& s);
	~String();
...
private:
	StringRep* rep;//Handle / Body(pipml:pointer to implementation)
};
//file String.cpp
//Body
#include "String.hpp"
namespace {
class StringRep{
friend class String;
	StringRep(const char* s);
	~StringRep();
	int count;
	char* rep;
};
}
  • 上例可以用来实现写时复制,即String中的指针指向同一个StringRep对象,StringRep内的rep指针再指向实际的字符串。

继承表示is a

  • public继承类似生物中的节目纲目科属种
  • 子类会继承父类的全部数据,可以理解为子类的对象有父类的成分
  • 顺序
    • 构造由内而外,派生类的构造函数先调用父类的构造函数
    • 析构由外而内,派生类的析构函数先执行自己,再调用父类的析构函数
  • 子类继承父类的函数指的是调用权,即子类可以调用父类的函数
  • 虚函数
    • 非虚函数:不希望派生类重新定义它(override)
    • 虚函数:希望派生类重新定义它,但是基类已经有默认定义了
    • 纯虚函数:派生类一定要重新定义它,一般基类完全没有定义该函数
class Shape{
public:
	virtual void draw() const = 0;//纯虚函数,画图形
	virtual void error(const std::string& mas);//虚函数,显示错误
	int objectID() const;//非虚函数
	...
};

class Rectangle: public Shape{ ... };
class Ellipse: public Shape{ ... };
  • 示例:软件中打开文件(设计模式:模板方法模式)
    • 用户输入文件名,程序检查文件名是否符合要求
    • 在磁盘中搜索文件
    • 除了打开文件这一步,其余的对于任何文件都是一致的,因为只有应用程序本身才知道如何读取自己的文件格式,将它设置为虚函数

继承+复合

  • 派生类中的非基类部分有复合关系
  • 基类部分有复合关系
    • 先构造有复合关系的类对象,再构造基类,最后是派生类

委托+继承

  • 实现给一份数据提供多个视图,当数据变化时,所有视图都要同步变化
  • 以下两个类之间是委托的关系
//存放真正的数据
class Subject
{
	int m_value;
	vector<Observer*> m_views;//准备一个容器,存放指针指向观察者
public:
	void attach(Oberser* 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;//更新观察者的数据
};	
  • 设计模式:组合模式
    • 设计一个文件系统,它既有文件,也有目录文件。且目录文件和文件可以相互嵌套
    • primitive表示普通文件,composite表示目录文件,要创建一个对象component既可以表示目录文件也可以表示普通文件,将二者设置为component的派生类。
//基类
class Component
{
	int value;
public:
	Component(int val):value(val){}
	virtual void add(Component*) {}//要被Composite重新定义,primitive不能重新定义它
};
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(Component* elem){
		c.push_back(elem);
	}
...
};
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐