文章目录
历史文章
1 Object Oriented Programming(OOP)
1.1 Composition(复合):has-a
class Queue<T>{
public:
bool empty(){return c.empty();}
size_t size(){return c.size();}
private:
Deque<T> c;
}
如果已经有Deque这样功能强大的class了,现在有一个特殊的需求,是这个Deque的功能子集,那么就可以使用Composition这种方式设计新的Queue,将Deque作为Queue的member。
Container--> Component
-
这种设计模式叫做Adapter
-
还要会计算class的size
-
构造函数,由内而外;析构函数,由外而内
1.2 Delegation(委托): Composition by reference
编译防火墙。
Handle不变,只改变Body(pImpl),指向不同的实现
多人共享同一个内存,但是需要的时候会copy on write。
1.3 Inheritance(继承):is-a
- base class的dtor必须是virtual,否则会出现undefined behavior
函数继承的是调用权。
2 虚函数与多态
2.1 Inheritance with virtual
三种函数:
- non-virtual 不希望子类重新定义
- virtual 子类可以重新定义,不必须,比如error,报错信息,比如add
- pure virtual 子类一定要重新定义, 比如draw,不同的子类应该有不同的行为。
虚函数搭配继承的最经典用法,这其实是23个设计模式之一,“Template Method”。
可以用于搭建应用程序的大框架,普通程序员根据框架去创建特定的应用程序。
这么做的原因是因为只有应用程序本身才知道怎么读取自己的文件格式。
2.2 Inheritance+Composition的构造和析构
谁先谁后?写代码验证。
2.3 Delegation + Inheritance的构造和析构(最强大)
2.3.1 Observer模式
场景:一个数据,有不同的几种方式去表达
其实是观察者模式, Observer
Subject和Observer的关系是delegation。
如果我想用图的形式去展示数据,以及使用表格的形式去展示数据,那么就设计下面的类即可,他们和Observer构成了继承关系。
class GraphObserver : public Observer{
...
}
class TableObserver : public Observer{
...
}
代码解析
#include <vector>
#include <iostream>
using namespace std;
//-------------------------------------------------------------------
// 问题: 同一个数据,想要用几种不同的方式去处理
//
// Subject和Observer是很早就写好的
// 开发应用程序的用户,想要创建自己业务场景的Observer,那么就继承Observer
// 事实上,可以看做ros中的callback机制的建议实现,用于数据更新
//-------------------------------------------------------------------
/**
* @brief: 最根本的父类
*/
class Observer {
public:
// 必须是纯虚函数,不同的子类应该有不同的行为
virtual void update(int value) = 0;
};
/**
* @brief: 管理所有观察者的类
*/
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 (auto &m_view : m_views)
m_view->update(m_value);
}
};
/**
* @brief: 观察者1
*/
class DivObserver : public Observer {
int m_div;
public:
// 创建观察者
DivObserver(Subject *model, int div) {
model->attach(this);
m_div = div;
}
DivObserver(int div) {
m_div = div;
}
// 重新实现update函数,用来接受Subject通知的更新信息
void update(int v) override {
cout << v << " div " << m_div << " is " << v/m_div << '\n';
}
};
/**
* @brief: 观察者2
*/
class ModObserver : public Observer {
int m_mod;
public:
ModObserver(Subject *model, int mod) {
model->attach(this);
m_mod = mod;
}
ModObserver(int mod) {
m_mod = mod;
}
// 重新实现update函数,用来接受Subject通知的更新信息
void update(int v) override {
cout << v << " mod " << m_mod << " is " << v%m_mod << '\n';
}
};
int main() {
//-------------------------------------
// 用法1,管理方式不同,有很多种实现方法
//-------------------------------------
cout << "method 1:\n";
Subject subj;
DivObserver divObs1(&subj, 4);
DivObserver divObs2(&subj, 3);
ModObserver modObs3(&subj, 3);
subj.set_val(14);
//-------------------------------------
// 用法2
//-------------------------------------
cout << "method 2:\n";
Subject subj2;
DivObserver divObserver1(4);
DivObserver divObserver2(3);
ModObserver modObserver3(3);
subj2.attach(&divObserver1);
subj2.attach(&divObserver2);
subj2.attach(&modObserver3);
subj2.set_val(14);
}
2.3.2 Composite模式(混合模式)
Primitive是最基础的一个组件,还有个混合组件,现在想要同时都能够添加这两种组件,那么就都共同继承同一个Component,注意其中的虚函数必须是非纯虚函数。但是在子类中又必须重新定义add
代码解析
#include <iostream>
#include <vector>
using namespace std;
//---------------------------------------------
// 问题: 待定补充。。。。
//---------------------------------------------
/**
* @brief: 最基础的组件,最原始的父类
*/
class Component {
public:
virtual void traverse() = 0;
};
/**
* @brief: 组件1
*/
class Primitive : public Component {
int value;
public:
Primitive(int val) {
value = val;
}
void traverse() override {
cout << value << " ";
}
};
/**
* @brief: 组件2,混合型,使用继承和委托
*/
class Composite : public Component {
vector<Component *> children;
int value;
public:
Composite(int val) {
value = val;
}
void add(Component *c) {
children.push_back(c);
}
// 是一种深度优先的递归
void traverse() {
cout << value << " ";
for (int i = 0; i < children.size(); i++)
children[i]->traverse();
}
};
class Row : public Composite {
public:
// Two different kinds of "container" classes. Most of the
// "meat" is in the Composite
Row(int val) : Composite(val) {}
void traverse() {
cout << "Row"; // base class.
Composite::traverse(); // 这貌似是一个深度优先的遍历哦
}
};
class Column : public Composite {
public:
Column(int val) : Composite(val) {}
void traverse() {
cout << "Col";
Composite::traverse();
}
};
int main() {
Row first(1); // Row1
Column second(2); // |
Column third(3); // +-- Col2
Row fourth(4); // | |
Row fifth(5); // | +-- 7
first.add(&second); // +-- Col3
first.add(&third); // | |
third.add(&fourth); // | +-- Row4
third.add(&fifth); // | | |
first.add(new Primitive(6)); // | | +-- 9
second.add(new Primitive(7)); // | +-- Row5
third.add(new Primitive(8)); // | | |
fourth.add(new Primitive(9)); // | | +-- 10
fifth.add(new Primitive(10)); // | +-- 8
first.traverse(); // +-- 6
cout << '\n';
}
2.3.3 Prototype(原型)
问题:父类希望能够看到未来多年后实现的子类里面的东西
解决: 使用Prototype设计模式
代码解析
#include <iostream>
using namespace std;
//---------------------------------------------------
// 问题:framework作为父类希望能够得到多年后创造的子类的信息
//---------------------------------------------------
enum imageType {
LSAT, SPOT
};
/**
* @brief: protype模式父类的定义
*/
class Image {
public:
// 【必须是纯虚函数】,子类必须重新实现
// 因为不同的子类,调用此函数的行为是不同的
virtual void draw() = 0;
// 【必须是static类型】,由prototype设计模式的调用方式决定
// 调用方法:Image::findAndClone(imageType);
// 当需要一个子类对应类型的实例对象的时候,通过调用该函数获取
// 原理:根据子类的返回类型,调用子类实现的clone函数,返回对应的一个子类对象
static Image *findAndClone(const imageType &type) {
for (int i = 0; i < _nextSlot; i++)
if (_prototypes[i]->returnType() == type)
return _prototypes[i]->clone();
// 万一没找对对应的type的处理方法
// 事实上不会出现,因为ImageType的对象是提前定义好的
// 如果传进来的是一个类型不符合的,编译期间就不会通过
std::cerr << "Wrong Image Type\n";
return nullptr;
}
protected:
// 【必须是纯虚函数】,用于获取子类的类型。子类中必须重新实现,告诉父类自己的类型情况
virtual imageType returnType() = 0;
// 【必须是纯虚函数】,子类重新实现,在里面创建一个自己,返回给父类调用
virtual Image *clone() = 0;
// 子类需要调用这个函数,相当于身份注册,告诉父类子类的存在
// 【不一定必须是static成员函数】。这个地方之所以写成static形式是为了表明他是一个公有的身份
static void addPrototype(Image *image) {
_prototypes[_nextSlot++] = image;
}
private:
// addPrototype() 将每一个子类的prototype保存在这里
// 【必须用static】,因为Image只有一份,所有的Image的Object都要存放在这里
static Image *_prototypes[10];
static int _nextSlot; // 下标移动,初始化为0
};
// static变量定义
Image *Image::_prototypes[];
int Image::_nextSlot;
/**
* @brief: 未来创造的新的子类
*/
class LandSatImage : public Image {
public:
// 告诉父类子类的类型,override表明显式重写
imageType returnType() override {
return LSAT;
}
// 该子类独有的行为
void draw() override {
cout << "LandSatImage::draw " << _id << endl;
}
// 父类调用想要clone的时候,调用的是子类的该函数
// 返回该子类的一个object
Image *clone() override {
return new LandSatImage(1);//调用的是一个精心设计的构造函数,传入的参数没有实际意义,只是为了不想调用默认构造函数
}
protected:
// 这是一个非常经典的写法,是prototype里面的一个核心细节。
// dummy并不会被用到,只是为了和默认构造函数相区分
// 返回该子类的对象的构造函数,只会在clone里面被调用,所以设置为private区域也可以
LandSatImage(int dummy) {
_id = _count++;
}
private:
// 只会在子类的static LandSatImage _landSatImage成员初始化的时候调用
LandSatImage() : _id(-1) {
addPrototype(this);
}
// 调用上面的默认构造函数,然后调用父类中的addPrototype
// 相当于将子类的type在父类的管理系统中进行了注册
// 也就是无论以后有多少个该子类的object,只需要注册一次就行,告诉父类,现在存在这样一种子类的类型存在
// 你可能要管理,父类就将子类的这种类型保存到了_prototypes[]数组中了
// 以后有需要创建该子类的时候,父类通过查询子类的类型就知道,哦,我有你这种类型
// 然后对应调用子类相对应的clone函数即可
static LandSatImage _landSatImage;
// 每一个该子类的实例都有一个独立的id
int _id;
static int _count; // id计数
};
LandSatImage LandSatImage::_landSatImage; // 初始化子类的static成员,告诉父类注册该子类的类型
int LandSatImage::_count = 1; // 每一个该子类的object都有一个不同的id
/**
* @brief: 未来创造的新的子类
*/
class SpotImage : public Image {
public:
imageType returnType() override {
return SPOT;
}
void draw() override {
cout << "SpotImage::draw " << _id << endl;
}
Image *clone() override {
return new SpotImage(1);
}
protected:
SpotImage(int dummy) {
_id = _count++;
}
private:
SpotImage() : _id(-1) {
addPrototype(this);
}
static SpotImage _spotImage;
int _id;
static int _count;
};
SpotImage SpotImage::_spotImage; // 初始化子类的static成员,告诉父类注册该子类的类型
int SpotImage::_count = 1; // 每一个该子类的object都有一个不同的id
int main() {
// 需要创建的图像请求输入流
const int NUM_IMAGES = 8;
imageType input[NUM_IMAGES] = {LSAT, LSAT, LSAT, SPOT, LSAT, SPOT, SPOT, LSAT};
// 创建原形类数组对象,管理未来的输入流
Image *images[NUM_IMAGES];
// 根据输入流的图像类型,父类调用findAndClone函数,根据子类的返回类型,调用clone函数
// 得到对应类型的子类object并返回,使用子类数组对象存储管理这些新创建的子类类型object
for (int i = 0; i < NUM_IMAGES; i++)
images[i] = Image::findAndClone(input[i]);
// 对每一个不同种类的对象,执行对应的不同操作
for (int i = 0; i < NUM_IMAGES; i++)
images[i]->draw();
// 释放原型数组对象,因为不是使用new[],所以应该逐个delete
for (int i = 0; i < NUM_IMAGES; i++)
delete images[i];
}