Featured image of post C++面向对象高级开发-继承、复合、委托与组合设计模式

C++面向对象高级开发-继承、复合、委托与组合设计模式

Composition 复合,Delegation 委托,Inheritance 继承,Inheritance+Composition继承复合结构,Delegation+Inheritance委托继承结构,设计模式总结

C++面向对象高级开发-继承、复合、委托与组合设计模式

Notes

  • Composition 复合
  • Delegation 委托
  • Inheritance 继承
  • Inheritance+Composition继承复合结构
  • Delegation+Inheritance委托继承结构
  • 设计模式总结

Composition 复合

template <class T, class Sequence = deque<T>>
class queue {
...
protected:
    Sequence c; //底层容器
public:
    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();}
};

当一个类中包含另外一个类对象时,可称为复合,表现为has-a关系。在上例中,queue与Sequence即deque的关系为: queue与deque的关系

由于queue的所有功能都由deque实现,即queue为deque的Adapter(适配器模式)。从内存占用上看,如图所示: 内存占用分析

queue中含有deque,deque中含有2个ITR <T>及2个指针,共40字节,1个ITR<T>含有4个指针,共16字节。

复合关系下的构造函数与析构函数

类名泛化后,Container复合Component。构造由内而外,当创建Container Object时,会执行其构造函数,在初值列中即调用Component的构造函数,而后在执行只属于自己部分的构造函数。红色部分的Component构造函数由C++编译器实现,无需手动写出。编译器自动使用默认构造函数,若需使用带参构造函数,则需在初值列覆盖原默认构造函数即可。析构由外而内,先完成Container独有的析构函数部分,最后执行Component的析构函数。

Delegation 委托

Delegation委托

图中示例代码与String字符串类设计中的代码高度相似,取自标准库。String字符串累设计中使用char* m_data;为字符串数据,图中String类使用String Presentation(StringRep*)对象存储字符串数据。在指向关系图中,菱形变为空心,代表菱形侧为指针。Delegation又可称为Composition by reference

Delegation与Composition的不同: Composition在Container对象创建时,Component对象同时创建,生命期同时开始。Delegation对象创建时,数据为指针引用,故在访问对象数据时指针所指向对象的生命周期才开始,故Delegation的对象与指针所指对象的生命周期不同步。

对于字符串类本身的设计在另外一个类中实现,本身只有接口设计时,此种设计方法为Point to Implementation,即pImpl,也可记为Handle/Body。设计为pImpl类设计时,由于接口类和实现类的分离,实现类可随时更换即为此设计的优点。此种设计又被称为编译防火墙。图中a、b、c为三个StringRep*指针,指向同一个StringRep对象,此时的引用计数值为3。

a、b、c的内容均为Hello,若a要更改Hello的值,单独拿一份给a修改数据,b和c仍然共享原数据。此时的行为被称作copy-on-write。写时写副本。

Inheritance 继承

struct _List_node_base
{
    _List_node_base* _M_next;
    _List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node
   : public _List_node_base
{
    _Tp _M_data;
};

代码取自标准库,_List_node继承自_List_node_base。继承关系如图所示: Structure继承

在上述代码中,注意继承部分前含public关键字,所以C++中支持public继承(表现为is-a关系)、private继承和protected继承。 Inheritance下的构造和析构函数

类名泛化后,Derived子类继承Base父类。子类对象包含父类成分,构造由内而外,析构由外而内。与Composition部分原理相同。红色部分的构造函数由C++编译器实现,无需手动写出。

设计规范10:

父类的析构函数必须为虚函数,否则会出现无定义行为。

虚函数: 虚函数简介

在成员函数之前增加virtual关键字,即为虚函数。函数的继承应从继承权角度考虑。不允许子类重新定义(Override)父类为非虚函数。希望派生类重新定义且含有默认定义时为虚函数。而派生类必须重新定义时为纯虚函数,无默认定义(纯虚函数可以有定义,此后续补充)。 带虚函数的继承

如一个文档程序在打开文件时,打开文档、检查文件名、查找文件、读取文件等操作在众多应用中都存在。由于每个程序读取文件的方式都不同,实例中OnFileOpen中的Serialize()即为虚函数。不为纯虚函数时,虚函数可为空函数。在使用时,创建子类对象,子类对象调用父类函数。实例中子类对象调用父类方法的全名为CDocument::OnFileOpen(&myDoc);,按序执行OnFileOpen时,到Serialize函数处通过其全名执行子类Override函数。将有固定的动作,且关键部分延缓至子类决定的函数的设计方法称为模板方法(Template Method)。此处的Template与C++的Template意义不同。在应用程序框架中会大量使用到模板方法。详细代码如下图所示: Template Method代码示例

Inheritance+Composition继承复合结构

继承与复合的构造和析构

图中上半部分一个子类继承自父类,且子类有一个Component,Base与Component存在于子类的对象中。可根据如下代码得出结论:

#include <iostream>
#include <cstdlib>

#define BOOL_OK 1

class Base;
class Derived;
class Component;

class Base {
public:
    Base();
    virtual ~Base();
};

inline Base::Base() {
    std::cout << "Class Base Constructor Executed." << std::endl;
}

inline Base::~Base() {
    std::cout << "Class Base De-constructor Executed." << std::endl;
}

class Component {
public:
    Component();
    ~Component();
};

inline Component::Component() {
    std::cout << "Class Component Constructor Executed." << std::endl;
}

inline Component::~Component() {
    std::cout << "Class Component De-Constructor Executed." << std::endl;
}

class Derived : public Base {
public:
    Derived();
    ~Derived();
    Component* comp1 = new Component;
};

inline Derived::Derived() {
    std::cout << "Class Derived Constructor Executed." << std::endl;
}

inline Derived::~Derived() {
    std::cout << "Class Derived De-constructor Executed." << std::endl;
    comp1->~Component();
}

int main(int argc, const char * argv[]) {
    Derived d1;
    return EXIT_SUCCESS;
}

执行结果为:

Class Base Constructor Executed.
Class Component Constructor Executed.
Class Derived Constructor Executed.
Class Derived De-constructor Executed.
Class Component De-Constructor Executed.
Class Base De-constructor Executed.
Program ended with exit code: 0

所以可得出结论,构造函数仍是由内而外,Derived的构造函数首先调用Base的default构造函数,然后调用Component的default构造函数后才执行Derived自身的构造函数。 Derived::Derived(...): Base(), Component() {...}; 析构函数仍是由外而内,Derived的析构函数首先执行Derived自身的析构函数,然后调用Component的析构函数,最后执行Base的析构函数。 Derived::Derived(...) {... ~Component(), ~Base()}; 图中下半部分为子类中有父类的部分,父类中含有Component部分,所以构造函数的调用次序依次为Component、Base、Derived。而析构函数的调用次序依次为Derived、Base、Component。

Delegation+Inheritance委托继承结构

Subject与Observer

Subject为存储数据的类,Observer为访问数据的类(窗口访问数据)。Subject可拥有多个Observer,Observer可派生子类,故可有多个Observer访问Subject数据,为Inheritance继承的关系,Subject与Observer为Delegation的关系。attach函数注册Observer对象,放入m_views容器中。notify函数为当数据产生变更时,遍历Observer对象调用update函数以更新数据。代码细节如下图: Subject与Observer代码细节

Composite

当考虑设计一个文件系统时,目录中可以存放文件,目录中还可以与其他目录或文件一起放在另一个目录中。如图中泛化所示,文件可用Primitive类表示,Composite类是一个容器,可容纳Primitive类,也可容纳自身类型,可创建Primitive类与Composite类的父类Component类,Primitive与Composite均继承自Component类,如图中的紫色框内部分所示。此种设计方法为Composite设计模式。

Prototype设计模式

类图规范说明:类图格式为对象名:类型名

  • 对象名前加下划线_表示该变量为静态变量
  • 方法名前加-号为private私有类型
  • 方法名前加+号为public默认类型
  • 方法名前加#号为protected类型

当考虑设计框架类时,其派生类可能在未来被创建时类名才会确定。所以在设计此类时,可考虑派生类创建自身副本作为原型,框架类可访问后通过复制即可创建其对应的派生类。在派生类中增加静态变量即创建了自身副本。创建自身副本时调用构造函数,因静态变量在类定义内部,故自身私有构造函数可以被调用。构造函数调用addPrototype(this);,此函数为继承自框架类函数,将传入的指向派生类的指针放入prototypes容器,此例为数组内部。clone函数直接返回派生类对象副本。框架类可以通过静态变量对象调用派生类的clone函数获得派生类对象副本。若将clone函数设为静态函数则不需静态变量的做法是错误的,因为静态函数的调用必须通过类名调用。代码如下:

Prototype代码1

在框架类Image中,clone函数被设计为纯虚函数,框架类自己无法实现clone函数,须由派生类实现,故设计为纯虚函数强制派生类实现clone函数。在private中有原型容器,此例为数组。findAndClone函数在原型容器中可能使用class名称或数组位元访问获取原型并调用clone函数。

注:class本体的private数据必须在类体外做一次定义。

Prototype代码2

在派生类中,clone函数直接返回派生类副本。在private部分声明静态变量_landSatImage,调用私有构造函数,调用addPrototype送至框架类(父类)。因在调用私有addPrototype父类会寻找并调用派生类的clone函数时会使用默认构造函数调用,所以在clone函数内部不可以再使用默认构造函数,故需要增加一个新的构造函数,不可以放在public区域的原因是需要通过原型构建新对象,所以新的构造函数为protected或private均可以。新构造函数可增加dummy参数区别于默认构造函数。

Prototype代码3

设计模式总结

  • Adapter 设计模式
  • Handle/Body 设计模式
  • Template Method 设计模式
  • Observer 设计模式
  • Composite 设计模式
  • Prototype 设计模式