您的位置:首页 > 编程语言 > C语言/C++

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

2015-09-14 14:40 891 查看
一,类与类之间的关系:继承(Inheritance)、复合(Composition)、委托(Delegation)。

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

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

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


其中第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 代码如下

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


  代码第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() }; // 红色表示编译器自动生成的


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

七,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() 等于 拷贝。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: