文章目录
现代软件专业分工之后的第一个结果是“框架和应用程序的划分","组件协作”模式通过 晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
典型模式:Template Method、Strategy、Observer/Event
模板方法模式(Template Method)
定义
定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类中。Template Method 使得子类可以不改变(复用)一个算法的结构即可重定义(override重写)该算法的某些特定步骤。
——《设计模式》GoF
动机
- 在软件构建过程中,对于某一项任务,它常常有稳定的整体结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
- 如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
结构图
代码
// 代表库函数
class Library{
public:
// 稳定 template method
void run(){
step1();
if(step2()){//支持变化==>虚函数的多态调用
step3();
}
for (int i = 0; i < 4; i++){
step4(); //支持变化 ==> 虚函数的多态调用
}
step5();
}
virtual ~Library(){}
protected:
void Stepl(){ //稳定
//.....
}
void Step3(){//稳定
//.....
}
void Step5(){ //稳定
//...
}
virtual bool Step2() = 0;//变化
virtual void step4() = 0;//变化
};
// 代表调库的应用(程序开发)
class Application : Library{
public:
virtual bool step2(){
//子类重写具体实现...
}
virtual void step4(){
//子类重写具体实现...
}
};
int main(){
Library* pLib = new Application();
lib.run();
delete pLib;
}
要点
- Template Method模式是—种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基础实现结构。
- 除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是Template Method的典型应用。
- 在具体实现方面,被Template Method调用的虚方法可以实现,也可以没有任何实现(抽象方法、纯虚方法),但它们一般被设置为protected方法。
总结
个人觉得模板方法模式使得类具有了更好的封装性,提供出了更加简单的实现接口。通过多态将早绑定转换为晚绑定、编译时绑定转换为运行时绑定,使得类库的使用更加方便灵活,便于使用。
策略模式(Strategy)
定义
定义一系列算法,把算法一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
——《设计模式》GoF
动机
- 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
- 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
结构图
代码
//税收计算策略(方式)
class Taxstrategy{
public:
//Context代表上下文信息
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 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及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
- Strategy模式提了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
- 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象(单例模式),从而节省对象开销。
总结
Strategy模式将策略(算法)封装成一个个对象,并继承自同一个基类,然后再通过多态指针动态的指定某个策略,使得整个代码扩展更加灵活,且不会影响客户程序的使用。
观察者模式(Observer/Event)
定义
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
——《设计模式》GoF
动机
- 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系"——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
- 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
结构图
代码
class IProgress{
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress(){}
};
// 文件分割器(将大文件分割成n个小文件)
class FileSplitter
{
string m_filePath;
int m_fileNumber;
//ProgressBar* m_progressBar;//具体通知控件
List<IProgress*> m_iProgressList;//抽象通知机制,支持多个观察者
public:
FileSplitter(const string& filePath, int fileNumber,
ProgressBar* progressBar):
m_filePath(filePath),
m_fileNumber(fileNumber){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i - 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue; //计算目前进度
onProgress(progressValue);//发布通知
}
}
//添加观察者(订阅)
void addIProgress(IProgress* iProgress){
m_iProgressList.add(iProgress);
}
//移除观察者(取消订阅)
void removeIProgress(IProgress* iProgress){
m_iProgressList.remove(iProgress);
}
protected:
virtual void onProgress(float value){
List<IProgress* >::Iterator itor = m_iProgressList.begin();
//每一个订阅者执行相应操作
while(itor != m_iProgressList.end()){
(*itor)->DoProgress(value);
itor++;
}
}
};
//主界面类
class MainForm : public Form, public IProgress{
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);
splitter.addIProgress(this);//订阅
splitter.split();
splitter.removeIProgress(this);//取消订阅
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
};
要点
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合;
- 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播;
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知;
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分;
总结
观察者模式早闻大名,在框架之中运用广泛。经过学习可以发现,观察者模式将原本的依赖关系发生转变,不在依赖于实现细节,转而依赖抽象接口。
如果在FileSplitter中直接定义一个具体的通知方式(如progressBar),那么不仅无法做到通知多个观察对象,而且这个通知被限定死了,将来要添加新的订阅者或者更换新的通知展示形式都需要更改FileSplitter类本身,造成紧耦合。