组件协作类设计模式
组件协作通过地址晚绑定,实现了框架与应用之间的松耦合。
类图模型就不放上来了,以理解代码和背后的含义为主
template method 模板方法
发生场景:对于某一项任务,常常有稳定的操作骨架,但是子步骤却需要很多改变的需求,或者由于固有的原因无法和整体结构同时实现,必须一早一晚。
原来代码
// 伪代码
class library//类库开发
{
public:
void step1(){};
void step3(){};
void step5(){};
private:
int i;
};
class application//应用开发
{
public:
void step2(){};
bool step4(){};
};
void main()//应用开发
{
library lib;//以某种流程串起来
application app;
lib.step1();
app.step2();
lib.step3();
if (app.step4()) lib.step5();
system("pause");
}
产生问题:
在程序开发过程中,库函数往往早于程序本身而开发,因此应用程序本身是晚开发的,
用应用程序调用早期开发的库函数属于早绑定;
解决方案:
在面向对象语言种,有一种晚绑定机制,就是一个早开发的库函数调用晚开发的函数。
template_method实现了使得子类可以复用一个算法的主结构即可重新定义某些特定的步骤。
// template method伪代码
class library//类库开发
{
public:
void run()
{
step1();
step2();
step3();
if (step4()) step5();
}
virtual ~library(){};
protected:
virtual void step2()=0;
virtual bool step4()=0;
private:
void step1(){};
void step3(){};
void step5(){};
int i;
};
class application:public library//应用开发
{
protected:
void step2()override {};
bool step4()override {};
};
void main()//应用开发
{
library *lib=new application();//利用虚函数的动态绑定规则,让子类的对象指向父类的指针,
//实现晚绑定
lib->run();
delete lib;
}
弊端:应用开发人员会变菜……
strategy 策略模式
发生场景:在软件构件中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法编写到对象当中,可能会使对象变得异常复杂,有时候支持不适用的算法也是一种性能负担。
因此我们需要根据需要透明地更改对象的算法,将算法本身和对象本身解耦。
原来代码
enum TaxBase
{
CN_tax,
US_tax,
DE_tax
//……
};
class Salesorder
{
TaxBase tax;
public:
double caculatetax()
{
if (tax == CN_tax)
{
//
}
else if (tax == US_tax)
{
//
}
else if (tax == DE_tax)
{
//
}
//……
}
};
弊端:
如果在后期,希望对原有的东西进行更改,比如多加一个国家a,那么就需要对enum以及elseif条件进行更改
这样就违背了 开放封闭原则:对扩展开放,对更改封闭;应该尽可能用扩展方式应对未来的变化
含有许多条件判断的代码就可以使用策略模式;比如if else 或者switch case这种
//更改后
struct context
{
int value;
};
class taxstrategy
{
public:
virtual double caculate(const context& con) = 0;
virtual ~taxstrategy(){};
};
class CNtax:public taxstrategy
{
public:
double caculate(const context& con) override
{
}
};
class UStax :public taxstrategy
{
public:
double caculate(const context& con) override
{
}
};
class DEtax :public taxstrategy
{
public:
double caculate(const context& con) override
{
}
};
//如果想加入其他国家在这里直接写新的继承就行,salesorder稳定了
//class ANothertax :public taxstrategy
//{
//public:
//double caculate(const context& con) override
//{
//}
//};
class Salseorder
{
private:
taxstrategy *stategy;//注意这里只能放一个指针,因为是抽象类,无法成为对象
//引用也行,但还是指针好
public:
Salseorder(strategyfactory * factory)//工厂,请看后续工厂模式讲解
{
this->stategy = factory->newstagegy();
}
~Salseorder()
{
delete stategy;
}
double caculatetax()
{
//
context con;
double val = stategy->caculate(con);//多态调用
//
}
};
优势:
具有良好的本地性:
代码在运行的时候,在内存里。最好的时候在高阶缓存里面调用最快,如果代码段过长,
需要放到主存甚至虚拟内存里面,如果冗长的代码可能会挤出其他代码运行变慢了就。
注:
strategy 可以用单例模式来替代,请看后续讲解
observer/event 观察者模式
发生场景:
在软件构建过程中,我们需要对某些对象建立通知依赖关系,即一个对象的状态发生改变,所有依赖对象(观察者)都得到通知,如果这样的依赖过于紧密,则软件不能很好地抵御变化。
观察者模式能使这种依赖关系得到弱化,实现松耦合。
原代码
#include<string>
using std::string;
class ui
{
public:
QPushButton pushbuttom;
};
//假设一个文件分片的小软件,有一个按钮,按一下就实现分片
class mainform :public ui//主界面
{
string path; //需要分片的文件路径
int num;//文件分片的数量
public:
//假设点击了一个按钮,则发生,qt的函数
connect(ui.pushbuttom, &QPushButton::clicked, this, [=](){
filespliter spliter(path, num);
spliter.split();
});
};
class filespliter
{
private:
string path;
int num;
public:
filespliter(string str, int innum) :path(str), num(innum){};
void split()
{
//读取文件
//.......
//分批次写入
for (int i = 0; i < num; i++)
{
//....
}
}
};
弊端:
如果有新的需求比如在界面上加一个进度条,那么一般人的做法:
在ui中新建一个bar,并且在filespliter中也加一个,那么:
只要将ui中的bar传入给filespliter类中的bar,并且在for循环里面不断更新就可以实现
但是,这样就违背了依赖倒置原则,
高层模块不应该依赖底层模块,二者都应该依赖于抽象
抽象不应该依赖实现细节,实现细节应该依赖于抽象
具体说,就是可能今天是要一个bar,明天就要一个label,后天就要打点标识进展,
因为实现方式经常可能变,那么修改就会很麻烦
依赖:
如果A依赖B,那么A编译时B必须存在才能编译成功
解决方法:
直观的理解,不要依赖bar,依赖bar的父类,但是这样理解很粗浅;
整整需要知道的是自己需要实现什么问题:
究其根源,我们只需要实现一个通知,我们可以用相当抽象的方式来表大通知而不是一个控件
#include<string>
using std::string;
class ui
{
public:
Qpushbuttom pushbuttom;
Qprogessbar progressbar;
};
//假设一个文件分片的场景
class Iprogress//虚基类
{
public:
virtual void doprogress(float value) const = 0;
virtual ~Iprogress(){};
};
class mainform :public ui,public Iprogress
//不推荐使用多继承,唯一使用的场景就是一个接口继承多个虚拟父类
{
string path; //需要分片的文件路径
int num;//文件分片的数量
public:
void doprogress(float value) const override
{
progressbar->setvalue(value);
}
//假设点击了一个按钮,则发生,qt的函数
connect(ui.pushbuttom, &QPushButton::clicked, this, [=](){
filespliter spliter(path, num,this);
//filespliter spliter(path, num,noter);当每次传入只能传一个观察者,
spliter.split();
});
};
class filespliter
{
private:
string path;
int num;
Iprogress *iprogress;//抽象的通知机制
public:
filespliter(string str, int innum,Iprogress* pro) :path(str), num(innum),iprogress(pro){};
void split()
{
//读取文件
//.......
//分批次写入
for (int i = 0; i < num; i++)
{
//....
float value = static_cast<float>(num);
value = (i + 1 )/ num;
onprogress(value);
}
}
protected:
void onprogress(float value)
{
if (iprogress != nullptr)
{
iprogress->doprogress(value);
}
}
};
//假如说我们要支持多个类当作观察者
class notifier :public Iprogress
{
public:
void doprogress(float value)const override
{
}
};
弊端:发现如果想支持多个观察值,每次都要加入新的,十分麻烦,因此针对多个观察者有以下改良版:
#include<string>
#include<vector>
#include<iostream>
using std::string;
using std::vector;
class ui
{
public:
Qpushbuttom pushbuttom;
};
//假设一个文件分片的场景
class Iprogress//虚基类
{
public:
virtual void doprogress(float value) const = 0;
virtual ~Iprogress(){};//抽象通知基类,这是重点
};
class mainform :public ui,public Iprogress
//不推荐使用多继承,唯一使用的场景就是一个接口继承多个虚拟父类
{
Qprogessbar progressbar;
string path; //需要分片的文件路径
int num;//文件分片的数量
public:
void doprogress(float value) const override
{
progressbar->setvalue(value);
}
//假设点击了一个按钮,则发生,qt的函数
connect(ui.pushbuttom, &QPushButton::clicked, this, [=](){
notifier *noter;
filespliter spliter(path, num);
//filespliter spliter(path, num,noter);当每次传入只能传一个观察者,
spliter.add_Iprogress(noter);
spliter.add_Iprogress(this);
spliter.split();
});
};
class filespliter
{
private:
string path;
int num;
vector<Iprogress*> iprogress;//抽象的通知机制,
//使用vector就可以支持多个观察者
public:
void add_Iprogress(Iprogress* subprogress)
{
iprogress.push_back(subprogress);
}
void remove_Iprogress(Iprogress* subprogress)
{
for (auto i = iprogress.begin(); i != iprogress.end();i++)
{
if (*i == subprogress)
{
iprogress.erase(i);
}
}
}
filespliter(string const str, int const innum) :path(str), num(innum){};
void split()
{
//读取文件
//.......
//分批次写入
for (int i = 0; i < num; i++)
{
//....
float value = static_cast<float>(num);
value = (i + 1 )/ num;
onprogress(value);
}
}
protected:
void onprogress(float value)
{
for (auto i : iprogress)
{
if (i != nullptr)
{
i->doprogress(value);
}
}
}
};
//假如说我们要支持多个类当作观察者
class notifier :public Iprogress//这个我希望他能打印*标识进度
{
public:
void doprogress(float value)const override
{
for (int i = 0; i < static_cast<int>(value); i++)
{
std::cout << "*" << std::endl;
}
}
};
优势:
随便添加多少个观察者无所谓,自己的结构稳定不变,我结构本身可以支持任意类型的观察者,
从而实现独立,也就是所谓的松耦合;
发送通知的时候不需要针对特定的观察者进行通知,而是针对整个抽象的基类进行通知;
抽象基类内部有自己的规则调用通知;
观察者可以自己决定是否需要订阅通知,也就是所谓的add以及remove;目标对象对此一无所知;