一,类与类之间的关系:继承(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 ;
}

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

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 = ; i < m_views.size(); ++i)
m_views[i]->update(this, m_value);
}
};

Observer类

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

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

Image.cpp

// 客户端需要客户端调用这个public static方法,当它需要Image的子类的实例时
Image *Image::findAndClone(imageType type)
{
for (int i = ; 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();
}
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 = ;

SpotImage.h

class SpotImage : public Image
{
public:
imageType returnType() {
return SPOT;
}
void draw() {
cout << "SpotImage::draw " << _id << endl;
}
Image *clone() {
return new SpotImage();
}
protected:
SpotImage(int dummy) {
_id = _count++;
}
private:
SpotImage() {
addPrototype(this);
}
static SpotImage _spotImage;
int _id;
static int _count;
};
SpotImage SpotImage::_spotImage;
int SpotImage::_count = ;

main.cpp

// 模仿创建请求的流 (Simulated stream of creation requests)
const int NUM_IMAGES = ;
imageType input[NUM_IMAGES] =
{
LSAT, LSAT, LSAT, SPOT, LSAT, SPOT, SPOT, LSAT
}; int main()
{
Image *images[NUM_IMAGES];
// 给予一个image类型,找到与之对应的原型,然后返回一个clone对象
for (int i = ; i < NUM_IMAGES; i++)
images[i] = Image::findAndClone(input[i]);
// 示例:正确的image对像被clone
for (i = ; i < NUM_IMAGES; i++)
images[i]->draw();
// 释放动态内存
for (i = ; i < NUM_IMAGES; i++)
delete images[i];
}

其中 LSAT 就是 LastSatImage。

3. clone() 等于 拷贝。

C++面向对象高级开发课程(第三周)的更多相关文章

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

    0. 内存分区 计算机中的内存在用于编程时,被人为的进行了分区(Segment),分为: -“栈区”(Stack) -“堆区”(Heap) -全局区(静态 区,Static) -文字常量区和程序代码区 ...

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

    1. 类中含有指针—— class with pointer member(s) ——的情况经常发生,典型的有:string 类. 2. STL中的 string 类太复杂,copy on write ...

  3. 20135302魏静静——linux课程第三周实验及总结

    linux课程第三周实验及总结 一.实验:跟踪分析Linux内核的启动过程 使用gdb跟踪调试内核从start_kernel到init进程启动 使用实验楼的虚拟机打开shell cd LinuxKer ...

  4. j2ee高级开发技术课程第三周

    一.分析Filter例子(轻量级javaee企业应用实战p132) // 执行过滤的核心方法 public void doFilter(ServletRequest request, ServletR ...

  5. Java EE开发技术课程第三周

    一.分析Filter例子: @WebFilter(filterName="log",urlPatterns={"/*"})//创建一个LOgFilter类pub ...

  6. 《Linux内核分析》课程第三周学习总结

    姓名:何伟钦 学号:20135223 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

  7. iOS 高级开发 runtime(三)

    三 .动态添加方法 我们可以通过runtime动态地添加方法.那么到底啥叫动态添加方法呢?动态添加方法就是当我们程序运行时才知道我们应该调用哪个方法.我们首先需要了解这一点,当我们编写完一段代码后,我 ...

  8. 面向对象程序设计--Java语言第三周编程题:查找里程

    查找里程 题目内容: 下图为国内主要城市之间的公路里程: 你的程序要读入这样的一张表,然后,根据输入的两个城市的名称,给出这两个城市之间的里程. 注意:任何两个城市之间的里程都已经给出,不需要计算经第 ...

  9. 网易Java高级开发课程随笔

    java学习也有6个月之久,记录下课程相关知识点,目前我还没有掌握,so仅作技术点记录 鉴于在.NET上我封装了一套开发框架,虽去年按.NET封装的思路自己也弄了个java开发框架,还是感觉对java ...

随机推荐

  1. slam学习资源

    从零开始学slam: http://blog.csdn.net/akunainiannian/article/details/45363731 史上最全的SLAM学习资料收集  http://www. ...

  2. codeforces 792D - Paths in a Complete Binary Tree

    #include<cstdio> #include<iostream> #define lowbit(x) x&(-x) typedef long long ll; u ...

  3. UVA - 11624 Fire! bfs 地图与人一步一步先后搜/搜一次打表好了再搜一次

    UVA - 11624 题意:joe在一个迷宫里,迷宫的一些部分着火了,火势会向周围四个方向蔓延,joe可以向四个方向移动.火与人的速度都是1格/1秒,问j能否逃出迷宫,若能输出最小时间. 题解:先考 ...

  4. stack overflow underflow

    Introduction to algorithms / Thomas H. Cormen...[etal.].—3rded. If we attempt to pop an empty stack, ...

  5. 眠眠interview Question

    1.  Wkwebkit在异步回调  如何像webview的回调 一样在主线程回调.可以使用runloop 解决么? dispatch get main queue http://www.jiansh ...

  6. 改革春风吹满地---hdu2036(多边形用差积求面积)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2036 #include<iostream> #include<stdio.h> ...

  7. Ubuntu下安装vsftpd

    1.sudo apt-get install vsftpd 2.修改配置文件 sudo gedit /etc/vsftpd.conf write_enable=YES ls_recurse_enabl ...

  8. maven报错集

    1.install报错解决[致命错误: 在类路径或引导类路径中找不到程序包 java.lang] Windows分隔符英文分号 <bootclasspath>${java.home}/li ...

  9. 对nodejs的理解(一)

    1.介绍一下事件驱动编程---快餐店点餐. 在基于线程的方式中(thread-based way)你到了柜台前,把你的点餐单给收银员或者给收银员直接点餐,然后等在那直到你要的食物准备好给你.收银员不能 ...

  10. 各版本 MySQL 并行复制的实现及优缺点

    MySQL并行复制已经是老生常谈,笔者从2010年开始就着手处理线上这个问题,刚开始两三年也乐此不疲分享,现在再提这个话题本来是难免“炒冷饭”嫌疑. 最近触发再谈这个话题,是因为有些同学觉得“5.7的 ...