组件协作模式
现代软件:框架与应用的划分
组件协作主要通过晚绑定来实现松耦合
- template method模板方法
- strategy策略模式
- observer/event观察者模式
1.模板方法template method
样板
动机:软件构件过程中对于某一项特定的任务常常有稳定的整体结构,但各个子步骤却有很多变化,先实现谁呢?如何在确定的整体结构的前提下来灵活应对各个子步骤的变化或晚期实现需求呢?
//程序库开发人员
class Library{
public:
void Step1(){
//...
}
void Step3(){
//...
}
void Step5(){
//...
}
};
//应用程序开发人员
class Application{
public:
bool Step2(){
//...
}
void Step4(){
//...
}
};
int main()
{
Library lib();
Application app();
lib.Step1();
if (app.Step2()){
lib.Step3();
}
for (int i = 0; i < 4; i++){
app.Step4();
}
lib.Step5();
}
//程序库开发人员
class Library{
public:
//稳定 template method
void Run(){
Step1();
if (Step2()) { //支持变化 ==> 虚函数的多态调用
Step3();
}
for (int i = 0; i < 4; i++){
Step4(); //支持变化 ==> 虚函数的多态调用
}
Step5();
}
virtual ~Library(){ }//有继承关系的基类的析构是虚函数
protected:
void Step1() { //稳定
//.....
}
void Step3() {//稳定
//.....
}
void Step5() { //稳定
//.....
}
virtual bool Step2() = 0;//变化
virtual void Step4() =0; //变化
};
//应用程序开发人员
class Application : public Library {
protected:
virtual bool Step2(){
//... 子类重写实现
}
virtual void Step4() {
//... 子类重写实现
}
};
int main()
{
Library* pLib=new Application();
lib->Run();
delete pLib;
}
}
结构化软件设计流程
面向对象软件设计流程
![image-20210506141700151](https://gitee.com/xsm970228/images2020.9.5/raw/master/20210506141702.png)
一个晚的东西调用早的东西——早绑定
一个早的东西调用晚的东西——晚绑定
模式定义:
定义一个操作中的算法的骨架(稳定),而将 一些步骤延迟(变化)到子类中。template method 使得子类可以不改变(复用)一个算法的结构即可重定义(override重写)该算法的某些特定步骤
- 稳定的写为正常函数
- 变化的要写为虚函数
设计模式的前提必须有稳定点
将小兔子放在笼子里让它活动,不能让它跳出来污染整个房子——设计模式!!!
红色的为稳定部分,蓝色的为变化的部分
要点总结:
- 非常基础性,常用,它用最简洁的机制(虚函数的多态)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构 继承+虚函数多态就是扩展
- 反向控制结构 不要调用我,让我来调用你
- 被template method调用的虚方法可以实现 也可以不实现 但一般设置为protected
2.策略模式strategy
动机: 在软件构建过程中,某些对象使用的算法可能是多种多样的,经常改变,如果将这些算法都编码到对象中,将会使对象变得很发杂,而且有时候支持不使用的算法也是一个性能负担。
如何在运行时根据需要透明的改变对象的算法?将算法与对象本身解耦,从而避免上述问题?
//计算税
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax,
FR_Tax //更改
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
else if (tax == FR_Tax){ //更改 违背开放封闭原则
//...
}
//....
}
};
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
//扩展
//*********************************
class FRTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//.........
}
};
class SalesOrder{
private:
TaxStrategy* strategy;//实现多态的变量一般用指针
public:
//工厂模式
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val =
strategy->Calculate(context); //多态调用
//...
}
};
模式定义: 定义一系列算法,把它们一个个封装起来,并且使他们可以互相替换(变化)。该模式使得算法可独立于它的客户程序(稳定)而变化(扩展,子类化)
要点总结:
- strategy及其子类提供了一系列可重用算法 使得类型在运行时方便的根据需要在各个算法切换
- 当if-elseif-else出现时,常常标志着strategy策略的出现,strategy策略模式提供了另一种if-else实现方式 但是当if-else是稳定的(一周7天)就用if-else就好
- 如果strategy对象没有实例变量,那么各个上下文可以共享同一个strategy对象从而节省对象开支
3.观察者模式observer/event
动机: 需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使得软件不能很好的抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系从而实现软件体系结构的松耦合
文件分割器:上古时代软盘copy大文件
//界面
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;//改变 编译时依赖 实现细节 以后的进度条可能是另外的形式 界面依赖于进度条(实现细节)
//通知
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, progressBar);//改变
splitter.split();
}
};
//分割功能
class FileSplitter
{
string m_filePath;
int m_fileNumber;
ProgressBar* m_progressBar;//改变
public:
FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
m_filePath(filePath),
m_fileNumber(fileNumber),
m_progressBar(progressBar){//改变
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
//改变
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue);
}
}
};
//提出新需求 文件分割进度条
//违背依赖倒置原则
//
//界面
class MainForm : public Form, public IProgress //c++多继承不推荐用 可能带来复杂耦合性问题 推荐一种多继承 一个主继承类 其他的为接口类
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, progressBar, this);
splitter.split();
}
virtual void doProgress(float value){
progressBar->setValue(value);
}
};
//文件分割部分
class IProgress{
publuc:
virtual void doProgress(float value) = 0;
virtual ~IProgress(){}
};
//整个文件分割类不依赖于任何界面元素 可以放在任何系统中使用
class FileSplitter
{
string m_filePath;
int m_fileNumber;
//ProgressBar* m_progressBar;//通知控件
IProgress* m_iprogress;//抽象通知机制
public:
FileSplitter(const string& filePath, int fileNumber, IProgress* iprogress) :
m_filePath(filePath),
m_fileNumber(fileNumber),
m_iprogress(iprogress)){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
// float progressValue = m_fileNumber;
// progressValue = (i + 1) / progressValue;
// m_progressBar->setValue(progressValue);
if(m_iprogress != nullptr){
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_iprogress->doProgress(progressValue);//更新进度条
}
}
}
};
//问题 如果要支持多个通知该怎么办 支持多个观察者
class MainForm : public Form, public IProgress //c++多继承不推荐用 可能带来复杂耦合性问题 推荐一种多继承 一个主继承类 其他的为接口类
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number, progressBar);
//支持多个观察者
splitter.addIProgress(this);//订阅通知
splitter.addIProgress(&cn);//订阅通知
splitter.split();
}
virtual void doProgress(float value){
progressBar->setValue(value);
}
};
//另一个观察者
class ConsoleNotifier : public IProgress{
public:
virtual void DoProgress(float value){
cout << value << endl;
}
};
class IProgress{
publuc:
virtual void doProgress(float value) = 0;
virtual ~IProgress(){}
};
//整个文件分割类不依赖于任何界面元素 可以放在任何系统中使用
class FileSplitter
{
string m_filePath;
int m_fileNumber;
//ProgressBar* m_progressBar;//通知控件
List<IProgress*> m_iprogressList;//抽象通知机制 最大的重点 支持多个观察者
public:
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber))){
}
void addIProgress(IProgress* iprogress){
m_iprogressList.add(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
// float progressValue = m_fileNumber;
// progressValue = (i + 1) / progressValue;
// m_progressBar->setValue(progressValue);
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);
}
}
protected:
void onProgress(float value){
for(auto iter = m_iprogressList.begin(); iter != m_iprogressList.end(); iter++)
(*iter)->doProgress(value);//多个观察者更新通知
}
}
};
模式定义:定义对象的一种一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象(subject)都能得到通知并自动更新
observer——Iprogress
ConcreteObserver——Mainform/ConSoleNotifier
Subject/ConcreteSubject——filesplitter(没有将对象抽象)
要点总结:
- 使用观察者模式使得我们可以独立地改变目标与观察者,从而使两者之间的依赖关系称为松耦合(随便添加多少个观察者都行,对象的结构是稳定的)
- 目标发送通知时无需指定观察者,通知自动传播(onProgress)
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知
- Observer模式是基于时间的UI框架中非常常用的设计模式,也是MVC模式重要的组成部分