C++面向对象高级开发课程(第三周)

一,类与类之间的关系:继承(Inheritance)、复合(Composition)、委托(Delegation)。

二,复合:表示 is-a ,该设计思想可以参照C语言的 struct 。

1. 例子:源自STL中queue的源代码。

 1 template <class T, class Sequence = deque<T> >
 2 class queue {
 3 ...
 4 protected:
 5     Sequence c; // 底层容器
 6 public:
 7     // 以下完全利用 c 的操作函數完成
 8     bool empty() const { return c.empty(); }
 9     size_type size() const { return c.size(); }
10     reference front() { return c.front(); }
11     reference back() { return c.back(); }
12     // deque 是兩端可進出,queue 是末端進前端出(先進先出)
13     void push(const value_type& x) { c.push_back(x); }
14     void pop() { c.pop_front(); }
15 };

其中第1行代码  class Sequence = deque<T> 表示 Sequence 默认是 deque<T> 类型。

以上的 queue 类也等价于把 deque<T> 替换进来。

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(); }
};

2. 对于这种类与类之间的关系,表示了 GoF 23 种设计模式中的 Adapter 适配器模式——类对象结构型模式。如下图表示:

3. 其中,queue 是容器,它改装了 deque——也就是重写了一些操作的方法名。比如原来 deque 的 push_back()改写成了 push();pop_front()改写成了pop()。

4. 复合(Composition)关系的 UML 表示法,如下图:

尤其重要的是表示类与类之间的“复合”关系。

5. 从内存的角度,复合关系在内存中的分布是这样的,如下图所示:

6. 构造与析构函数的执行流程

6.1  构造函数由内而外:Container 的构造函数首先调用 component 的 default 构造函数,然后才执行自己。

Container::Container(...) : Component() {...} ;

   构造函数后面的单引号“:”表示委托构造函数语法,同时也是构造函数初始值列表。

  上面那段代码中红体字部分,表示是由编译器加入的,如果不满足程序员应用的新情况,程序员可以自行修改或者加入参数。 

6.2  析构函数由外而内:Container 的析构函数首先执行自己,然后才调用 Component 的析构函数。

Container::~Container(...) {... ~Component() };

上面那段代码中红体字部分,表示是由编译器加入的。

三,委托:Composition by reference

1. Composition by reference 不能根据现实情况——用指针“组合”到一起——表示为 by pointer。是因为学术界不说 pointer 而是 reference。该课程也从来不提 by pointer。

2. UML表示如下图:

其中,空心的表示内部用指针而不是实体来“组合”,所以有点虚,用空心的菱形表示。

3. 例子 String.h 代码如下

 1 // file String.hpp
 2 class StringRep;
 3 class String {
 4 public:
 5     String();
 6     String(const char* s);
 7     String(const String& s);
 8     String &operator=(const String& s);
 9     ~String();
10 . . . .
11 private:
12     StringRep* rep; // pimpl
13 };

  代码第12行:pimpl 表示 pointer to implementation 。pimpl 是一种设计模式。“左边的类”是接口,“右边的类”是具体实现。此方法也称“编译防火墙”

   文件 String.cpp 代码如下

// file String.cpp
#include "String.hpp"
namespace {
class StringRep {
    friend class String;
        StringRep(const char* s);
        ~StringRep();
        int count;
        char* rep;
    };
}

String::String(){ ... }
...

   “委托”关系类之间的生命周期不同步:因为类与类之间的“组合”方式是 reference ,所以声明周期不同步。

4. String 只是对外的接口,当需要任何实现时都会去调用 StringRep 这个具体的实现类的操作。这种设计模式是 GoF 23 当中的 Bridge 桥接模式,也称 Handle and Body 模式。 

  该设计模式有利于“实现方法”的切换,很具有弹性。这样的方法也叫“编译防火墙”左边只编译一次,右边可以变换实现方式。

5. String类 和 StringRep类 的画图表示如下图

三个对象共享一个数据空间:Hello。此时的reference counting,也就是 n 的值是 3。

6. 当有其中某个对象需要改变数据时,程序会分配一个新的数据空间给它让他去修改,这样的设计方式也称作“copy on write”。

四,继承:表示 is-a。

1. 继承与虚函数搭配最强有力。

2. UML图表示如下

其中空心的三角形表示继承,且父类与子类一定要保持上下位置。

(表示继承的空心三角形) 

(放块 T 表示该类是模版类型)

对应UML的代码如下

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;
};

   在C++中,struct 同样具有继承的功能。

3. 继承关系的UML图表示如下图

4. 从内存分配的角度看继承关系

5. bass class 的 dtor(析构函数)必须是 virtual function,否则会出现编译错误:undefined behavior。

6. 构造与析构函数的执行流程

6.1  构造由内而外:Derived(派生类)的构造函数首先调用 Base 的 default 构造函数,然后才执行自己。

Derived::Derived(...) : Base() {...};

   上面那段代码中红体字部分,表示是由编译器加入的,如果不满足程序员应用的新情况,程序员可以自行修改或者加入参数。 

6.2  析构由外而内:Derived 的析构函数首先执行自己,然后才调用 Base 的析构函数。

Derived::~Derived(...) { ... ~Base() };

   上面那段代码中红体字部分,表示是由编译器加入的。

7. 继承权限有三种(public、private、protected)public在C++中最常用,并且Java语言只有 public 继承权限。

五,Inheritance with virtual functions

1. 大部分的继承,都是继承的函数调用权。(因为数据都是放在 private 区)

2. non-virtual 函数 : 不希望 derived class(子类)重新定义(override)覆盖它。

  virtual 函数 : 希望 derived class 重新定义(override)它,且对它已有的默认定义进行覆盖。

  pure-virtual 函数 : 希望 derived class 一定要重新定义它,且目前对它函数本身无任何默认定义。

  代码以及示例如下图:

3. 虚函数 :可以完成子类 override 的操作。类似 PHP mvc 框架中 IndexController 继承 Controller 之后,在 IndexController::init() 中定义的“前操作”。

4. virtual functions 的作用:延缓父类中的动作到子类中去写出来。是设计模式 Template Method 的基础。

比如下面这段节选自 MFC 中的代码。用到的设计模式是GoF 23 中 Template Method (模版方法)——类行为型模式。

4.1 UML图如下

4.2 Application framework 代码(MFC官方定义的基础类)如下

CDocument::OnFileOpen()
{
    ...
    Serialize(); // 读取不同类型文件的方法暂时未知,所以交由子类去实现
    ...
}

 4.3 Application 代码(程序员应用MFC基础类,继承之后的代码实现)如下

class CMyDoc::public CDocument
{
    virtual Serialize() 
    {
        // users do something
    }
};

 4.4 在主程序中,对于用户自定义类的用法如下

main()
{
    CMyDoc myDoc;
    …
    myDoc.OnFileOpen();
}

 4.5 程序执行流程,如下图

第一步:初始化 myDoc 类。

第二步:调用 myDoc.OnFileOpen() 方法,编译器会自动寻径到父类(CDocument)中的OnFileOpen() 方法。

第三步:编译器发现 serialize() 方法是虚函数,所以会自动向“下”寻径(CMyDoc),去用子类的 serialize() 来覆盖父类中的 serialize()。

4.6 具体的代码实现如下图

六,Inheritance + Composition 关系下的构造和析构

1.情况一:复合发生在子类中

1.1 UML图如下

1.2 内存分布如下图

1.3 代码实现

class Component
{
public:
    Component() { std::cout << "component" << std::endl;  }
};

class Base
{
public:
    Base() { std::cout << "base"<< std::endl; }
};

class Derived : public Base
{
public:
    Derived() { std::cout << "derived" << std::endl; }
private:
    Component _component;
};

int main()
{
    Derived d;
    return 0;
}

 1.4 运行结果,如下图

1.5 结论:在 inheritance + composition 设计模式下,当复合发生在子类时,编译器的执行流程是 base->component->derived。

Derived::Derived(…): Base(),Component() { … }; // 红色字体表示编译器自动生成的

2.情况二:复合发生在父类中

2.1 UML,如下图

2.2 内存分布,如下图

2.3 代码实现

class Component
{
public:
    Component() { std::cout << "component" << std::endl;  }
};

class Base
{
public:
    Base() { std::cout << "base"<< std::endl; }
private:
    Component _component;
};

class Derived : public Base
{
public:
    Derived() { std::cout << "derived" << std::endl; }
};

int main()
{
    Derived d;
    return 0;
}

 2.4 运行结果

2.5 结论:在 inheritance + composition 设计模式下,当复合发生在父类时,编译器的执行流程是 component->base->derived。

Derived::~Derived(…){ … ~Component(), ~Base() }; // 红色表示编译器自动生成的

至此,类与类之间的三种关系已经交待完毕,下面是实际应用的部分。

七,Delegation(委托) + Inheritance(继承)

1. 这种设计模式在 UI 程序设计中最常用到,功能也是最强大的。

2. GoF 23 observer 观察者模式

2.1实例代码 Subject类 

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);
    }
};

 Observer类

class Observer
{
public:
    virtual void update(Subject* sub, int value) = 0;
};

 2.2. UML图如下

2.3. 窗口(window)就是 observer 类。

2.4 实际代码如下图

2.5 对 2.4 中代码的解释

2.5.1 注册的动作

void attach(Observer* obs)

   把观察者注册到本体(被观察者)中。

2.5.2 通知观察者的动作

void notify()

   通知观察者,进行更新数据。

   以上就是OOD(面向对象设计) 

至此,已经学习了 Adapter、Handle-Body、Template Method、Observer四中设计模式。还将学习Composition、Prototype两种设计模式。

八,委托相关设计

1. 设计一个文件系统。采用 GoF 23 当中的 Composite(组成)模式——对象结构型模式。

    其中  primitive  adj.基本的  表示文件

      composite  n.合金   表示容器

1.1 Composite 设计模式目的:容器中既能放置自己(composite),又能放置 primitive。

1.2 UML

1.3 代码

Component类

class Component
{
    int value;
public:
    Component(int val) { value = val; }
    virtual void add( Component* ) { }
};

 primitive类

class Primitive: public Component
{
public:
    Primitive(int val): Component(val) {}
};

 composite类

class Composite: public Component
{
    vector <Component*> c;
public:
    Composite(int val): Component(val) { }
    void add(Component* elem) 
    {
        c.push_back(elem);
    }
    …
};

 1.4 在C++的容器中(比如vector)只能存储每一个占用内存大小都是一样的类型,所以composite类中容器存储的是指针,尤其对于用户自定义的类型,更要存储指针。 

2. 设计一个框架。采用 GoF 23 当中的 Prototype(原型) 模式——对象创建型模式。 

2.1 Prototype 设计模式的目的:在不可能知道子类名称的时候,创建子类对象。

  C++原型模式中子类创建了一个自己,就是所谓的原型

  该设计模式突破了C++不知道类名而无法创建该类对象的束缚。

  比如如下php代码可以动态地创建一个类。

$obj = new $className();

2.2 UML

2.2.1 UML图中,带有下划线的表示是静态成员。

   (成员函数、成员变量)标识符:类型名

    “-”负号表示 private

    “#”井号表示protected

2.3 代码

Image.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;
    // 每一个声明过的Image的子类,注册它自己的原型 
    static void addPrototype(Image *image)
    {
        _prototypes[_nextSlot++] = image;
    }
private:
    // addPrototype()函数在这里存储每一个注册过的原型 
    static Image *_prototypes[10];
    static int _nextSlot;
};
Image *Image::_prototypes[];
int Image::_nextSlot;

 Image.cpp

// 客户端需要客户端调用这个public static方法,当它需要Image的子类的实例时
Image *Image::findAndClone(imageType type)
{
    for (int i = 0; i < _nextSlot; i++)
        if (_prototypes[i]->returnType() == type)
            return _prototypes[i]->clone();
}

 LandSatImagine.h

class LandSatImage : public Image
{
public:
    imageType returnType() {
        return LSAT;
    }
    void draw() {
        cout << "LandSatImage::draw " << _id << endl;
    }
    // 当clone()函数被调用时,它调用带有一个无用参数的构造函数
    Image *clone() {
        return new LandSatImage(1);
    }
protected:
    // 这个构造函数只能被clone()函数调用
    LandSatImage(int dummy) {
        _id = _count++;
    }
private:
    // 原理:初始化Image子类将会引起注册过的子类原型的默认构造函数被调用
    static LandSatImage _landSatImage;
    // 只有private static数据成员被初始化后,才会被调用
    LandSatImage() {
        addPrototype(this);
    }
    // 名词性的“state”是每一个实例的机制(Nominal "state" per instance mechanism)
    int _id;
    static int _count;
};
// 注册子类原型 
LandSatImage LandSatImage::_landSatImage;
// 初始化“state”是每一个实例的机制(Initialize the "state" per instance mechanism)
int LandSatImage::_count = 1;

 SpotImage.h

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++;
    }
private:
    SpotImage() {
        addPrototype(this);
    }
    static SpotImage _spotImage;
    int _id;
    static int _count;
};
SpotImage SpotImage::_spotImage;
int SpotImage::_count = 1;

 main.cpp

// 模仿创建请求的流 (Simulated stream of creation requests)
const int NUM_IMAGES = 8;
imageType input[NUM_IMAGES] =
{
    LSAT, LSAT, LSAT, SPOT, LSAT, SPOT, SPOT, LSAT
};

int main()
{
    Image *images[NUM_IMAGES];
    // 给予一个image类型,找到与之对应的原型,然后返回一个clone对象
    for (int i = 0; i < NUM_IMAGES; i++)
        images[i] = Image::findAndClone(input[i]);
    // 示例:正确的image对像被clone
    for (i = 0; i < NUM_IMAGES; i++)
        images[i]->draw();
    // 释放动态内存
    for (i = 0; i < NUM_IMAGES; i++)
        delete images[i];
}

 其中 LSAT 就是 LastSatImage。

3. clone() 等于 拷贝。

原文地址:https://www.cnblogs.com/fengyubo/p/4806294.html