“组件协作”模式:现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。典型模式:Template Method、Strategy、Observer / Event。

一、模板模式

1.动机

在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因 (比如框架与应用之间的关系)而无法和任务的整体结构同时实现。

2.作用

在确定稳定操作结构的前提下,灵活应对各个子步骤的变化或者晚期实现需求。

3.定义

定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类中。模板模式使得子类可以不改变(复用)一个算法的结构即可重定义(重写)该算法的某些特定步骤。

4.代码

//原有代码
//应用程序开发人员
class Application{
public:
bool Step2(){
//...
}
void Step4(){
//...
}
};
int main()
{
Library lib();
Application app();
lib.Step1();
if (app.Step2()){
lib.Step3();
}
for (int i = ; i < ; i++){
app.Step4();
}
lib.Step5();
}
//程序库开发人员
class Library{
public:
void Step1(){
//...
}
void Step3(){
//...
}
void Step5(){
//...
}
};
//运用模板模式后的代码
//应用程序开发人员
class Application : public Library {
protected:
virtual bool Step2(){
//... 子类重写实现
}
virtual void Step4() {
//... 子类重写实现
}
};
int main()
{
Library* pLib=new Application();
pLib->Run();
delete pLib;
}
}
//程序库开发人员
class Library{
public:
//稳定 template method
void Run(){
Step1();
//支持变化 ==> 虚函数的多态调用
if (Step2()) {
Step3();
}
for (int i = ; i < ; i++){
//支持变化 ==> 虚函数的多态调用
Step4();
}
Step5();
}
virtual ~Library(){ }
protected:
void Step1() { //稳定
//.....
}
void Step3() {//稳定
//.....
}
void Step5() { //稳定
//.....
}
virtual bool Step2() = ;//变化
virtual void Step4() =; //变化
};

5.解析

这是运用一个Application的应用和Library的框架,共同完成一项任务。该实现有稳定的过程步骤,而具体步骤2、4存在变化性。

在原有的代码中,Library框架开发人员负责具体步骤1、3、5的实现,Application应用开发人员负责过程步骤、具体步骤2、4的实现。实现时,让Application去寻找(依赖)Library的具体步骤。但实际中,应用开发一般基于框架开发进行,即框架开发先于应用开发,且应用开发人员一般需要直接面对客户需求,即其代码常常面临变化,如果将稳定过程步骤也交给应用开发人员,会将程序中稳定和变化的部分揉在一起,不利于维护。

在运用模板模式后的代码中,稳定的过程步骤、具体步骤1、3、5由框架开发人员实现,并给出了变化步骤2、4的接口,由应用开发人员在其派生类中实现,实现了程序中稳定和变化的部分分离,使程序更加健壮。

6.结构

AbstractClass(抽象类,如Library):

  1. 定义抽象的原语操作,具体的子类将重新定义它们以实现一个算法的各步骤;
  2. 实现一个模板方法,定义一个算法骨架。

ConcreteClass(具体类,如Application):实现原语操作以完成算法中与特定子类相关的步骤。

7.总结

  1. 模板模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性) 为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。

2. 除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是模板模式的典型应用,即指父类调用子类的操作。(早绑定和晚绑定)

二、策略模式

1.动机

在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。

2.作用

在运行时根据需要透明地更改对象的算法;将算法与对象本身解耦,从而避免上述问题。

3.定义

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。

4.代码

//原有的代码
enum TaxBase {
CN_Tax,
US_Tax,
FR_Tax //更改
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == FR_Tax){ //更改
// FR***********
}
}
};
//运用策略模式后的代码
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=;
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 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); //多态调用
}
};

5.解析

这是一个税费计算程序,而各个国家有不同的税费计算方法。现有CN、US两国的税费计算方法,由于业务扩展,该程序现需增加法国税费的计算方法。

在原有的代码中,在TaxBase枚举中增加FR的选项,并在SalesOrder:: CalculateTax()函数中扩展(if…else)语句。该方法在简单结构中看似简单,却严重违背了“开放封闭原则”的设计原则。

在运用策略模式后的代码中,不同国家对应着不同的行为(税费计算方法)。TaxStrategy是税费算法接口,具体实现在其派生类中实现。在SalesOrder类中,根据实际new出来的TaxStrategy(指针调用),在程序运行(不是编译)时调用具体的税费算法。该模式提供了一种替代继承的方法,并消除了一些条件语句,并且在需要增加/删除某国税费算法时,采用扩展方法,遵循了“开放封闭原则”的设计原则。

6.结构

Strategy(策略,如TaxStrategy): 定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。

ConcreteStrategy(具体策略,如CNTax、USTax、FRTax): 以Strategy接口实现具体算法。

Context(上下文,如Context): 可定义一个接口来让Strategy来访问它的数据。

  1. 用一个ConcreteStrategy对象来配置;
  2. 维护一个对Strategy对象的引用;

7.总结

1.Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得组件在运行时方便地根据需要在各个算法之间进行切换。

2.Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。

3.如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。

三、观察者模式

1.动机

在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。

2.作用

使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

3.定义

定义对象间的一种一对多(变化)的依赖关系,以便当一个 对象(subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

4.代码

//原有代码
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 = ; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + ) / progressValue;
m_progressBar->setValue(progressValue);
}
}
};
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 IProgress{
public:
virtual void DoProgress(float value)=;
virtual ~IProgress(){}
};
class FileSplitter
{
string m_filePath;
int m_fileNumber;
//IProgress* m_iprogress; //支持一个观察者
List<IProgress*> m_iprogressList; // 抽象通知机制,支持多个观察者
public:
// 一个观察者的更新,多个观察一般采用添加和删除观察者的形式
// FileSplitter(const string& filePath, int fileNumber, IProgress* m_iprogress) :
// m_filePath(filePath),
// m_fileNumber(fileNumber),
// m_iprogress(iprogress){
// }
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber),{ }
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = ; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + ) / progressValue;
onProgress(progressValue);//发送通知
}
}
//添加观察者和删除观察者
void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
virtual void onProgress(float value){
List<IProgress*>::iterator itor=m_iprogressList.begin();
List<IProgress*>::iterator end=m_iprogressList.end();
for( ; itor != end; itor++ )
(*itor)->DoProgress(value); //更新进度条
}
}
};
//C++支持多继承,但一般不推荐多继承,一种特殊情况是一个基类,其它都是接口
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());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //订阅通知
splitter.addIProgress(&cn); //订阅通知
splitter.split();
splitter.removeIProgress(this);
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
};
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};

5.解析

这是一个文件分割程序。当文件过大时,将文件分割成若干份小文件分别读取,在这过程中需要通知用户分割完成进度。

在原有的代码中,FileSplitter是文件分割工具类,构造函数中包含文件路径、文件数目以及进度对象;FileSplitter:: split()函数用于实现文件分割和进度计算。但上述代码存在一个问题,即进度条表现形式ProgressBar是一个具体类,即容易根据实现需求发生变化,如进度条、数字等形式都能表现完成进度。而MainForm类编译时会依赖它,即高层类依赖细节实现,违反依赖倒置原则。

运用观察者模式后的代码中,IProgress是一个通知进度的抽象类,可将进度消息通知给需要知道的对象(例如屏幕等),具体怎么通知在其子类中实现。MainForm类继承IProgress,并订阅了消息,可在MainForm:: DoProgress()中具体实现进度展现形式;此外,还增加了另外一个通知对象cn,并在ConsoleNotifier:: DoProgress()定义了另一种具体实现进度展示的方法。该代码至少有两点优势:编译时MainForm依赖抽象类IProgress,不会违反依赖倒置原则;进度展示形式多样,可根据实际需求变化。

6.结构

Subject(目标,如FileSplitter):

  1. 目标知道它的观察者,可以有任意多个观察者观察同一个目标;
  2. 提供注册和删除观察者对象的接口。

Observer(观察者,如IProgress):为那些在目标发生改变时需要获得通知的对象定义一个更新接口;

ConcreteSubject(具体目标,如FileSplitter)

  1. 将有关状态存入各ConcreteObserver对象;
  2. 当它的状态发生改变时,向它的各个观察者发出通知。

ConcreteObserver(具体观察者,如MainForm,ConsoleNotifier)

  1. 维护一个指向ConcreteSubject对象的引用;
  2. 存储有关状态,这些状态应与目标状态保持一致;
  3. 实现Observer的更新接口使自身状态与目标状态保持一致。

7.总结

  1. Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
  2. 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
  3. 观察者自己决定是否需要订阅通知,目标对象对此一无所知。

Observer模式是基于事件的UI框架中非常常用的设计模式,也是 MVC模式的一个重要组成部分。

学习记录:《C++设计模式——李建忠主讲》3.“组件协作”模式的更多相关文章

  1. 学习记录:《C++设计模式——李建忠主讲》6.“状态变化”模式

    状态变化模式:在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定.状态变化模式为这一问题提供了一种解决方案. 典型模式:状态模式(State).备忘录 ...

  2. 学习记录:《C++设计模式——李建忠主讲》1.设计模式

    1.学习目标 1)理解松耦合设计思想: 2)掌握面向对象设计原则: 3)掌握重构技法改善设计: 4)掌握GOF核心设计模式: 2.定义 每个设计模式描述了一个在我们周围不断重复发生的问题,以及该问题解 ...

  3. 学习记录:《C++设计模式——李建忠主讲》2.面向对象设计原则

    1.课程内容: 重新认识面向对象:面向对象设计原则: 2.重新认识面向对象 1)理解隔离变化:从宏观层面来看,面向对象的构建方式更能适应软件的变化,将变化所带来的影响减为最小: 2)各司其职:从微观层 ...

  4. 学习记录:《C++设计模式——李建忠主讲》5.“对象性能”模式

    对象性能模式:面向对象很好地解决了抽象地问题,但是必不可免地要付出一定地代价.对于通常情况来讲,面向对象地成本大都可以忽略不计,但某些情况,面向对象所带来地成本必须谨慎处理. 典型模式:单件模式(Si ...

  5. 学习记录:《C++设计模式——李建忠主讲》4.“单一职责”模式

    单一职责模式:在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任. 典型模式:装饰模式(Decorator).桥 ...

  6. 学习记录:《C++设计模式——李建忠主讲》7.“领域规则”模式

    领域规则模式:在特定领域中,某些变化虽然频繁,但可以抽象为某种规则.这时候,结合特定的领域,将问题抽象为语法规则,从而给出该领域下的一般性解决方案. 典型模式:解释器模式(Interpreter). ...

  7. 工厂模式(整理自李建忠<C++设计模式>视频)

    整理自李建忠<C++设计模式>视频 一.导入:"对象创建"模式和工厂模式 工厂模式只是该模式下的一种. 二.举例说明 有这样一个场景:需要在MainForm中设计一个按 ...

  8. 设计模式---组件协作模式之模板方法模式(Tempalte Method)

    前提:组件协作模式 现代软件专业分工之后的第一个结构是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常见的模式. 我们常常使用框架来写自己的 ...

  9. C++设计模式 之 “组件协作”模式:Template Method、Strategy、Observer

    “组件协作”模式: #现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式. #典型模式: Templ ...

随机推荐

  1. vue —— Toast 内 加变量

    toast正常使用: 在页面内引入: import { Toast } from 'mint-ui' 使用的时候,简单到飞起: Toast('领取成功'); 而如果想在toast中加入变量,也很简单: ...

  2. 近万字案例:Rancher + VMware PKS实现全球数百站点K8S集群管理

    Sovereign Systems是一家成立于2007年的技术咨询公司,帮助客户将传统数据中心技术和应用程序转换为更高效的.基于云的技术平台,以更好地应对业务挑战.曾连续3年提名CRN,并且在2012 ...

  3. ElasticSearch安装及使用

    ElasticSearch安装及使用 ELK由Elasticsearch.Logstash和Kibana三部分组件组成. Elasticsearch 是个开源分布式搜索引擎,它的特点有:分布式,零配置 ...

  4. < 配置jupyer notebook遇到的问题 - 500 : Internal Server Error >

    < anaconda配置jupyer notebook遇到的问题 - 500 : Internal Server Error > 问题描述: 我的jupyer notebook是在anac ...

  5. Leetcode(10)正则表达式匹配

    Leetcode(10)正则表达式匹配 [题目表述]: 给定一个字符串 (s) 和一个字符模式 (p).实现支持 '.' 和 '*' 的正则表达式匹配. '.' 匹配任意单个字符. '*' 匹配零个或 ...

  6. Kubernetes入门学习--在Ubuntu16.0.4安装配置Minikube

    目 录 一. 安装minikube环境 1.1. 安装前准备 1.2. 安装Lantern 1.2.1. Lantern下载网站 1.2.2. Lantern下载地址 1.2.3. Lantern安装 ...

  7. 优化 .net core 应用的 dockerfile

    优化 .net core 应用的 dockerfile Intro 在给 .net core 应用的写 dockerfile 的时候一直有个苦恼,就是如果有很多个项目,在 dockerfile 里写起 ...

  8. Android dos操作

    adb shell                    开Androidls                                列表cd +目录名                 打开目 ...

  9. carousel.html

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. SpringCloud之Eureka服务注册与发现(一)

    一 Eureka的基本架构 Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现(请对比Zookeeper). Eureka 采用了 C-S 的设计架构 ...