C++面向对象(四)Composition, Delegation, Inheritance

  • 一、Composition(复合)
    • - 关系表示为:has-a
    • - 其构造和析构的关系
  • 二、Delegation(委托)
    • - 其关系表示为:composition by reference
  • 三、Inheritance(继承)
    • - 其表示关系为:is-a
    • - 其构造和析构的关系
    • - virtual functions(虚函数)
  • 四、多态
    • (一)Inheritance + Composition
    • (二)Delegation + Inheritance
      • - 一个设计模式:Observer
      • - 一个设计模式:Composite
      • - 一个设计模式:Prototype

一、Composition(复合)

- 关系表示为: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();}// deque 是两端进出,queue 是末端进前端出(先进先出)void push(const value_type& x){c.push_back(x);}void pop(){c.pop_front();}
}

由图可知,queue里面直接包含了deque,所以为复合关系。且其为同时存在的,一有container,便马上也有了component。

其中为一种设计模式:Adapter


从内存的角度看,也体现其包含复合的关系:

- 其构造和析构的关系


(1)构造由内而外 (从里面开始一层一层搭建)

container的构造函数首先调用Component的 default 的构造函数,然后才执行自己。

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

(2)析构由外而内 (从外面开始一层一层剥离)

container的析构函数首先执行自己,然后才调用Component的析构函数。

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

注意:我们在写Container的构造或析构函数时,无需将Component的也加进去,因为红色部分是编译器自动帮我们加进去的。

二、Delegation(委托)

- 其关系表示为:composition by reference

 可以类似看成一种,由指针相连,指针包含的复合,而指针一般在学术界是用引用来描述的。

其跟上面Composition有类似的地方,但不同的一点是,其左边包含拥有的右边并不是同时产生同时存在的,即左边的先创建,右边的只有当指针有使用、调用到时才产生右边的函数对象(不同步),因而成为委托的关系。左边只是对外的接口(保持不变的),真正的实现在右边操作(可变动的),也称为编译“防火墙”。

三、Inheritance(继承)

- 其表示关系为:is-a

继承有三种:public,private,protected
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_date;
};

关系如图:

- 其构造和析构的关系


(1)构造由内而外 (从里面开始一层一层搭建)

Derived的构造函数首先调用Base的 default 的构造函数,然后才执行自己。

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

(2)析构由外而内 (从外面开始一层一层剥离)

Derived的析构函数首先执行自己,然后才调用Base的析构函数。

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

注意:我们在写Base的析构函数时,必须将其设置成虚函数,否则无法出现析构由外而内的操作行为。

- virtual functions(虚函数)

在继承的关系里面,所以的东西都可以被继承下来,数据可以被继承下来(即占用内存的一部分),函数也可以被继承下来,但函数的继承不应该从内存的角度去理解,而是从函数的调用权上去理解。子类继承父类,则子类可以拥有相应的父类函数的调用权。而这又引出了子类是否需要去改写或重新定义从父类继承的函数即虚函数的问题。

所以父类的成员函数可以分为三种:
(1)non-virtual 函数:不希望derived class 重新定义(override)它。
(2)virtual 函数:希望derived class 重新定义(override)它,且对它已有默认定义。
(3)pure virtual 函数:希望derived class 一定要重新定义(override)它,且对它没有默认定义。

class Shape{public:virtual void draw() const = 0;  // pure virtualvirtual void error(const std::string& msg); // impure virtualint objectID() const;       // non-virtual...
}
class Rectangle: public Shape{...
};
class Ellipse: public Shape{...
};

int objectID() const;
这个函数是产生个类似于编号号码的东西,不需要让下面的子类去重新定义它,在父类上的定义已经足够了,所以将其设置成非虚函数。

virtual void error(const std::string& msg);
这个函数表达错误信息,比如打开文件出错时,弹出个错误提示信息,但希望子类能给出更具体更精细的错误提示信息,所以将其设置成虚函数,希望在父类默认定义的基础上子类对其进行override,以表达更进一步的意思。

virtual void draw() const = 0;
这个函数作为父类根本不知道怎么去定义它,所以一定要子类去override它,所以将其设置成纯虚函数。

一个设计模式:Template Method

父类(Application framework)的框架可以是很多年前就写好的,但是里面的读取文件的函数Serialize()可以是多年前没办法完成的,因为之前不知道要怎样具体去实现,所以应将其设置成虚函数或者纯虚函数,让具体应用的子类(Application)去编写它。

所以设置框架时,将能固定下来的功能都写好,然后留下来的不确定的部分,就让它成为虚函数,然后让具体子类去override它。

执行过程:
1.在main里面创建个子类对象 myDoc;
2.通过子类对象调用父类函数OnFileOpen(),而此时相当于 CDocunment::OnFileOpen(&myDoc);
即传进去的是子类对象的指针 &myDoc;
3.因而在父类的函数中调用Serialize()时,this->Serialize(),这里的this指针便是子类对象&myDoc,所以又跳回到子类定义的virtual Serialize()函数中,然后运行。

#include <iostream>
using namespace std;class CDocument{public:void OnFileOpen(){// 这是个算法,每个cout代表一个实际动作cout << "dialog..." << endl;cout << "check file status..." << endl;cout << "update all views..." << endl;Serialize();cout << "close file..." << endl;cout << "update all views..." << endl;}virtual void Serialize(){}; // 注意,一定要将此成员函数设置为虚函数,否则子类无法override它
};class CMyDoc: public CDocument{public:virtual void Serialize(){// 只有应用程序本身才知道如何读取自己的文件(格式)cout << "CMyDoc::Serialize()" << endl;}
};int main(){CMyDoc myDoc;myDoc.OnFileOpen();
}

四、多态

(一)Inheritance + Composition


(1)构造由内而外 (从里面开始一层一层搭建)

Derived的构造函数首先调用Base的 default 构造函数,
然后调用Component的default 构造函数,
然后才执行自己。

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

(2)析构由外而内 (从外面开始一层一层剥离)

Derived的析构函数首先执行自己,
然后才调用Component的析构函数
然后才调用Base的析构函数。

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

因为子类继承的是父类,所以在执行顺序上,先将继承的东西实现了,再去实现自己本体里面所包含的东西(即先调用父类Base的构造函数,再调用自己本体里面包含的Component的构造函数)。

(二)Delegation + Inheritance

- 一个设计模式:Observer

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);}
};class Observer{public:virtual void update(Subject* sub, int value) = 0 // 纯虚函数
};class Observer1: public Observer{int m_div;
public:Observer1(Subject* model, int div){model -> attach(this);m_div = div;}virtual void update(int v){...}
};class Observer2: public Observer{int m_mod;
public:Observer2(Subject* model, int mod){model -> attach(this);m_mod = mod;}virtual void update(int v){...}
}int main(){Subject subj;Observer1 o1(&subj, 4);Observer1 o2(&subj, 3);Observer2 o3(&subj, 3);subj.set_val(14);
}

构想是让Subject可以拥有很多很多的Observer,所以准备了个容器去存放它,且存放的是Observer的指针对象,即delegation。

vector<Observer*> m_views;

而Observer又有很多的观察对象,即可以被继承,所以设置为父类,则其成员函数要定义为虚函数,以便继承的子类去override相应的内容。

virtual void update(Subject* sub, int value) = 0

执行过程:
1.在main里面创建框架委托主体类对象 subj;
2.创建o1,o2,o3父类对象;
3.在定义的子类Observer1中,利用指针&subjk,调用框架委托主体对象的函数subj -> attach();
4.进入委托主体对象的执行部分,调用其成员函数attach(),将o1加进去容器m_views中,实现注册功能(o2,o3也执行类似步骤,加进去容器当中);
5.框架委托主体类对象 subj调用成员函数set_val(),设置m_value值为14,然后继续调用成员函数notify();
6.在调用notify()时,因为调用指针是m_views[0] 即o1,所以转到Observer父类的update()虚函数,然后由于继承和由子类指针所指向,进而跳到子类对象o1的成员函数 virtual void update(int v),执行相应操作。(o2,o3也执行类似步骤,执行各自对虚函数override的内容)。

- 一个设计模式:Composite


设计思路:

Primitive是基础个体,而Composite是组合物,即Composite要设计一个容器,既要容纳Primitive的类对象,又要可容纳自身的Composite类对象。所以创建出一个父类Component,通过让Primitive和Composite去继承它,然后Composite里的容器便可通过放置Component对象实现:既可容纳Primitive的类对象,又可容纳自身的Composite类对象。

另外由于容器里面放置的东西大小需一致,所以为了将不同东西放进容器因而选择放置指针进去,即Component*。

而由于要加东西,所以Composite类里面定义了成员函数add(e:Component*),同样需要使用父类指针作为参数,表示即可添加Primitive的类对象,又可添加自身的Composite类对象。
注意:因为Primitive作为一个基础类,是没有实现添加功能的函数,所以在继承父类Component时是无法override其成员函数add()的,而Composite作为组合类,是需要override成员函数add()以添加不同的对象。因此,父类Component中的成员函数add()应设置为默认定义为空的虚函数(即无动作),而不能设置为纯虚函数(必须子类override)。

- 一个设计模式:Prototype

设计一个父类框架需要包含未创建出来的子类class对象,即未来子类需要自己创建出一个对象并被框架看到识别(子类自己去创建自己),从而copy出一份进行操作处理。

LSAT: LandSatImage // 冒号右边表示类型,下划线表示创建出来的是静态对象LSAT
-LandSatImage(int) // 短破折号表示是private,#表示是protected,无符号表示public

父类应该在多年前创建出一个空间,然后子类通过调用父类中的成员函数,将多年后创建出来的子类对象加入到父类多年前准备好的空间中。所以创建出来的子类对象的构造函数中有继承父类的成员函数addPrototype(),而该函数在父类中对应着添加进prototypes[10]中的操作。

// 父类
#include <iostream.h>
enum imagaeType{LSAT,SPOT
};class Image{public:virtual void draw() = 0;static Image* findAndClone(imageType);
protected:virtual imageType returnType() = 0;virtual Image* clone() = 0;//As each subclass of Image is declared, it registers its prototypestatic void addPrototype(Image* image){ _prototypes[_nextSlot++] = image;}
private:// addPrototype() saves each registered prototype herestatic Image* _prototypes[10];static int _nextSlot;
};
Image* Image::_prototypes[];
int Image::_nextSlot;
// Client calls this public static member function when it needs an instance of an Image subclass
Image* Image::findAndClone(imageType type){for(int i = 0; i < _nextSlot; i++)if(_prototypes[i] -> returnType() == type)return _prototypes[i] -> clone();
}

一个class里面静态的data,一定要在class本体的外头给定义,因为其是在外头才给内存的。

Image *Image::findAndClone(imageType type){
for(int i = 0; i < _nextSlot; i++)
if(_prototype[i] -> returnType() == type)
return _prototype[i] -> clone();
}
表示在容器_prototype[i]里面找到符合传入参数的类型的那个子类指针对象,然后clone()一份出来。

成员函数clone()因为不知道未来子类对象的具体类型,所以无法设置成静态函数,因为静态函数是需要知道具体类型的。所以在父类中,将其设置成纯虚函数,且一定要在子类中根据其具体类型去override它。

成员函数findAndClone()只是单纯地将未来的子类对象添加进容器中,所以可以设置为静态函数。

// 子类
class LandSatImage: public Image{public:imageType returnType(){return LSAT;}void draw(){cout << "LandSatImage::draw " << _id << endl;}// When clone() is called, call the one-argument ctor with a dummy argImage* clone(){return new LandSatImage(1);}
protected:// This is only called from clone()LandSatImage(int dummy){_id = _count++;}
private:// Mechanism for initializing an Image subclass -this causes the default ctor to be called, which registers the subclass's prototypestatic LandSatImage _landSatImage;// This is only called when the private static data member is initedLandSatImage(){addPrototype(this);  // this指代的便是前面的_landSatImage的指针,里面记录了_id信息。}// Nominal "state" per instance mechanismint _id; static int _count;
};
// Register the subclass's prototype
LandSatImage LandSatImage::_landSatImage;
// Initialize the "state" per instance mechanis
int LandSatImage::_count = 1;

先创建个静态的变量_landSatImage,同时在创建_landSatImage调用构造函数LandSatImage(),则构造函数里面的成员函数addPrototype(this)被调用,this指针即是*_landSatImage,这时便跳到父类的静态成员函数addPrototype()中从而将新创建的相应的子类添加到_prototype[]容器中。

将子类指针对象放到父类的容器当中后,当发生需要调用findAndClone()函数时,则需要调用其中的纯虚函数clone(),此时在子类中override的clone()需要 new LandSatImage,所以意味着也要调用构造函数LandSatImage(),但如果像之前那样调用构造函数,则又会继续创建新的子类指针对象加入到_prototype[]容器当中。
所以子类在override纯虚函数clone()时,需另外写一个构造函数即LandSatImage(int dummy),通过加入一个传参数来区别两个不同的构造函数。而第二个构造函数放在private或protected区域都可,但不可放在public区域,因为不希望被外界调用,此处选择放置protected区域。

// 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];// Given an image type, fnd the right prototype, and return a clonefor(int i = 0; i < NUM_IMAGES; i++)images[i] = Image::findAndClone(input[i]);// Demonstrate that correct image objects have been clonedfor(int i = 0; i < NUM_IMAGES; i++)images[i] -> draw();// Free the dynamic memorryfor(int i = 0; i < NUM_IMAGES; i++)delete images[i];// delete[] images;
}

执行过程:
1.父类Image一开始就生成静态的data:prototype[ ]指针数组 和 _nextSlot;
子类LandSatImage、SpotImage一开始也均生成静态的data: _landSatImage,_count 和 _spotImage,_count。
而子类在生成静态对象_landSatImage(_spotImage)时调用了构造函数,则其里面的成员函数addPrototype()也被调用(继承自父类的addPrototype() 函数),即参数传入子类指针去调用父类的addPrototype()函数(即将子类指针对象添加进父类的指针数组 _prototype[ ])。
而且因为class LandSatImage在前,所以_prototype[0] =LandSatImage*,而_prototype[1] = SpotImage*( 注意此时的_prototype[ ]数组长度只有2,因为只有两种子类)。
2.创建父类指针数组对象 images[i] ,(通过类名Image::)调用父类成员函数findAndClone(input[i]),且传入参数为子类类型。在findAndClone()函数中进行类型判断(在 _prototype[ ]数组中看类型是否有与传入参数类型匹配的),如果符合,则跳入相应的子类中去调用clone()函数,通过第二种构造函数LandSatImage(1)【或SpotImage(1)】,记录对应的_id,然后以指针类对象的数据形式返回到 image[i] 内。

C++面向对象(四)Inheritance, Composition, Delegation相关推荐

  1. C++进阶_Effective_C++第三版(六) 继承与面向对象设计 Inheritance and Object-Oriented Design

    继承与面向对象设计 Inheritance and Object-Oriented Design 面向对象编程已经风靡编程界,关于继承.派生.virtual函数等等需要深入了解. 32.确定你的pub ...

  2. 学java教程之面向对象(四)

    学编程吧学java教程之面向对象(四)发布了,欢迎通过xuebiancheng8.com来访问 本次课来分析java面向对象之构造方法.什么是构造方法呢,构造方法听名字顾名思义,构造的时候执行的方法就 ...

  3. 重构类关系-Replace Inheritance with Delegation以委托取代继承十一

    重构类关系-Replace Inheritance with Delegation以委托取代继承十一 1.以委托取代继承 1.1.使用场景 某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数 ...

  4. python自学篇十[ 面向对象 (四) :王者荣耀小游戏+模拟一个简单的银行进行业务办理的类]

    python基础系列: python自学篇一[ Anaconda3安装 ] python自学篇二[ pycharm安装及使用 ] python自学篇三[ 判断语句if的使用 ] python自学篇四[ ...

  5. java面向对象特征及阐述,Java面向对象四个特征

    Java面向对象有四个特征:抽象.封装.继承.多态.其中封装.继承.多态又被称为Java的基本特征. 抽象: Java中会把客观事物抽象成一个类.类就是封装了数据以及操作这些数据的代码逻辑实体.用字符 ...

  6. Python之面向对象四

    面向对象进阶 一.关于面向对象的两个内置函数 isinstance   判断类与对象的关系    isinstance(obj,cls)检查obj是否是类 cls 的对象,返回值是bool值 issu ...

  7. python学习之面向对象(四)

    6.9 反射 反射是非常重要的内置函数之一. 反射是通过字符串去操作对象相关的属性,这里的对象包括:实列,类,当前模块,其他模块. 主要的四个函数: 查看: hasattr(对象,待查内容的字符串形式 ...

  8. Replace Inheritance with Delegation(以委托取代继承)

    某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据 重构:在子类中新建一个字段用来保存超类,调整子类函数,令它改而委托超类,然后去掉两者的继承关系

  9. 【Go】面向对象(四):面向对象实战(图书借阅系统demo)

    package mainimport "fmt"type Book struct{BookId stringName stringPrice float64Author strin ...

最新文章

  1. R语言常用的统计函数
  2. 贝叶斯网络之父Judea Pearl力荐、LeCun点赞,这篇长论文全面解读机器学习中的因果关系...
  3. Python可视化应用实战-如何制作酷炫的图表?
  4. 浏览器阻挡cookies_解决WordPress登录提示”Cookies被阻止或者您的浏览器不支持”...
  5. java面试-Java并发编程(九)——批量获取多条线程的执行结果
  6. MVC View 中 html 属性名与关键字冲突问题的分析与解决
  7. openjdk7的ImageIO.read()导致jvm异常crash
  8. 任务调度之Oozie详解
  9. 【SVN】Eclipse SVN插件下载安装
  10. DataCastle[猜你喜欢]赛事算法分享
  11. 滴滴上市年营收超千亿,程维:我必须时刻保持危机感
  12. Windows11在Edge浏览器中打开IE浏览器兼容的页面,在Edge浏览器打开加载ActiveX控件的页面
  13. 针孔相机畸变成像详解
  14. 「算法」FWT(快速沃尔什变换)
  15. download failed: assets/main/import/09/09f53264-cd95-4751-99ad-d516c164dd80._cconb.dbbin, status: 40
  16. 光子晶体制作出LED
  17. Gym-101502J(取数博弈+dp)
  18. xtuoj1404菱形 II
  19. 3.计算机的应用领域及其发展趋势是什么,计算机应用的现状及其发展趋势
  20. Kerberos在linux安装部署

热门文章

  1. 通过ping查找和确定网络故障节点
  2. 基于stm32的数控线性稳压电源,恒压恒流电源资料
  3. 关于BitmapFactory.decodeStream(is)方法无法正常解码为Bitmap对象的解决方法
  4. PFlash和DFlash的区别
  5. 源码分析 @angular/cdk 之 Portal 1
  6. JavaScript 字符串格式化输出
  7. java实现杨辉三角形,蓝桥杯杨辉三角
  8. android手机系统检测工具下载,手机设备信息检测软件下载-设备信息检测appv1.0 官方版-腾牛安卓网...
  9. mysql添加用户出错_mysql添加用户inset报错1346解决办法
  10. 计算机一级文字录入输入法,怎么快速练习计算机打字?(鱼人私语的回答,10赞)...