c++设计模式笔记

发布时间:2024年01月20日

c++设计模式 author cjq 202401

参考链接: 图说设计模式 — Graphic Design Patterns

  • c++设计模式
    • 行为型模式
      • 中介者
      • 命令模式
      • 观察者
      • 策略
      • 状态
    • 分支主题
      • 装饰器
      • 适配器
      • 桥接
      • 享元模式
      • 代理
      • 外观
    • 创建型模式
      • 简单工厂
      • 工厂模式
      • 抽象工厂

创建型模式:

一:简单工厂模式

///
//  Factory.cpp
//  Implementation of the Class Factory
//  Created on:      01-十月-2014 18:41:33
//  Original author: colin
///

#include "Factory.h"
#include "ConcreteProductA.h"
#include "ConcreteProductB.h"
Product* Factory::createProduct(string proname){
  if ( "A" == proname )
  {
    return new ConcreteProductA();
  }
  else if("B" == proname)
  {
    return new ConcreteProductB();
  }
  return  NULL;
}
//缺点:就是在创建新的工厂需要更改代码逻辑,存在外部有一个不同的条件去判断产生工厂,违背开闭原则
//优点:通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性
//分离对象的创建和使用

二:工厂方法

在简单工厂的模式下,每一个对象的创建都使用一个工厂类去管理,但出现新的对象时,具体工厂类不需要更改。通过原先的传递模式、名称等,改为外部传递工厂实例?

工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • ConcreteFactory:具体工厂
class pro {
public:
    pro() {};
    virtual void use()=0;
};

class createpro:public pro {
public:
    createpro() {};
    void use()
    {

    };
};
//如果新加产品:只需要增加一个createpro_2和新加一个createfac_2
/*
class createpro_2:public pro {
public:
    createpro_2() {};
    void use()
    {

    };
};
class createfac_2 :public fac {
public:
    createfac_2 () {};
    pro* facmethoc()
    {
        return new createpro_2();
    }
};
*/
class fac {
public:
    fac() {};
    virtual pro* facmethoc()=0;
};

class createfac :public fac {
public:
    createfac() {};
    pro* facmethoc()
    {
        return new createpro();
    }
};

int main(int argc, char* argv[])
{
    fac* fc = new createfac();
    pro* prod = fc->facmethoc();
    prod->use();

    delete fc;
    delete prod;

    return 0;
}

优点:

  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”

缺点:

  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度

总结:

  • 工厂方法模式的主要优点是增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;其缺点在于增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
  • 工厂方法模式适用情况包括:一个类不知道它所需要的对象的类;一个类通过其子类来指定创建哪个对象;将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。

三:抽象工厂

工厂方法只适用一个工厂类生产一种具体的产品,但需要一个工厂类可以生产多个产品时,变换为抽象方法。

  • 当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。
  • 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
  • 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率

定义:

提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式

  • AbstractFactory:抽象工厂 ( 一个)
  • ConcreteFactory:具体工厂 (多个 里面可以生产多个不同产品类型的工厂)
  • AbstractProduct:抽象产品 (多个 每一个抽象产品可以生产多个不同产品类型的产品)
  • Product:具体产品 (多个,分配到不同的抽象产品中)
class pro {
public:
    pro() {};
    virtual void use() = 0;
};

class pro2 {
public:
    pro2() {};
    virtual void use() = 0;
};

class createpro:public pro {
public:
    createpro() {};
    void use()
    {

    };
};
class createpro2 :public pro2 {
public:
    createpro2() {};
    void use()
    {

    };
};

class createproB :public pro {
public:
    createproB() {};
    void use()
    {

    };
};
class createproB2 :public pro2 {
public:
    createproB2() {};
    void use()
    {

    };
};

class fac {
public:
    fac() {};
    virtual pro* facmethoc()
    {
        
    }
    virtual pro2* facmethocB()
    {

    }
};

class createfac :public fac {
public:
    createfac() {};
    pro* facmethoc()
    {
        return new createpro();
    }
    pro2* facmethocB()
    {
        return new createproB2();
    }
};
class createfac2 :public fac {
public:
    createfac2() {};
    pro* facmethoc()
    {
        return new createproB();
    }
    pro2* facmethocB()
    {
        return new createpro2();
    }
};

int main(int argc, char* argv[])
{
    fac* fc = new createfac();
    pro* prod = fc->facmethoc();
    pro2* prod2 = fc->facmethocB();
    prod->use();
    prod2->use();
    delete fc;
    delete prod;
    delete prod2;
    return 0;
}
/*
 * waveproduct.h
 *
 *  Created on: 2023年8月21日
 *      Author: cjq
 */
//工厂方法的几种结合使用
#ifndef __POW_WAVE_PRODUCT_H__
#define __POW_WAVE_PRODUCT_H__

#include "driver/wavelib/pwr_wave.h"
#include "common/paramdef.h"
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#if MUTIL_WAVE
static float harm_Hz[HARM_SIZE] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50};
//static Poco::Mutex g_waveCtrl_mtx;
#endif
/抽象工厂/

extern bool CheckWaveParam(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value,float &Peak);

class BuiltWave
{
public:
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, const void* pVal4, const void* pVal5,const void* pVal6,bool flag, float value)=0;
  virtual bool Check(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, const void* pVal4, const void* pVal5,const void* pVal6,bool flag, float value) = 0;
protected:
  float m_harmoutfamp[PWR_PHASE_QTY][HARM_SIZE];
  float m_harmpeak[PWR_PHASE_QTY];
};

class Sine : public BuiltWave
{
public:
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, const void* pVal4, const void* pVal5,const void* pVal6,bool flag, float value)
  {
    if (!Check(nVal, pVal1, pVal2, pVal3, pVal4, pVal5,pVal6,flag, value))
      return NULL;
    WaveBase<int32_t, WAVE_DISP_POINTS>* mm = CreateWave<int32_t, WAVE_DISP_POINTS>(WAVE_TYPE_SINE);
    mm->SetParam(nVal, pVal1, pVal2, pVal3,pVal4, pVal5, pVal6,flag, value);
    mm->GenWave();
    return mm;
  };
  virtual bool Check(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value) { return true; };
};

class Squre : public BuiltWave
{
public:
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value)
  {
    if (!Check(nVal, pVal1, pVal2, pVal3,pVal4, pVal5,pVal6, flag, value))
      return NULL;
    WaveBase<int32_t, WAVE_DISP_POINTS>* mm = CreateWave<int32_t, WAVE_DISP_POINTS>(WAVE_TYPE_SQUARE);
    //处理数据检查计算
    mm->SetParam(nVal, pVal1, pVal2, pVal3, pVal4, pVal5,pVal6,flag, value);
    mm->GenWave();
    return mm;
  };
  virtual bool Check(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value) { return true; };
};

class CommonWave : public BuiltWave
{
public:
  CommonWave(BASE_WAVE_TYPE type):m_Wavetype(type){

  };
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, const void* pVal4, const void* pVal5,const void* pVal6,bool flag, float value)
  {
    if (!Check(nVal, pVal1, pVal2, pVal3, pVal4, pVal5,pVal6,flag, value))
      return NULL;
    WaveBase<int32_t, WAVE_DISP_POINTS>* mm = CreateWave<int32_t, WAVE_DISP_POINTS>(m_Wavetype);
    //处理数据检查计算
    PrintfLog("************************m_Wavetype=%d,pVal1=%f\n",m_Wavetype,*(float*)pVal1);
    mm->SetParam(nVal, pVal1, pVal2, pVal3,pVal4, pVal5,pVal6, flag, value);
    mm->GenWave();
    return mm;
  };
  virtual bool Check(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value) { return true; };
private:
  BASE_WAVE_TYPE m_Wavetype ;
};
#if MUTIL_WAVE
class Dts : public BuiltWave
{
public:
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, bool flag, float value)
  {
    //处理数据检查计算
    if (!Check(nVal, pVal1, pVal2, pVal3, flag, value))
      return NULL;
    WaveBase<int32_t, WAVE_DISP_POINTS>* mm = CreateWave<int32_t, WAVE_DISP_POINTS>(WAVE_TYPE_DTS);
    //mm->SetParam(nVal, pVal1, pVal2, pVal3, flag, value);
    int phase = nVal;
    if(CheckWaveParam(nVal, pVal1, pVal2, pVal3, flag, value,m_harmpeak[phase]));
    memcpy(m_harmoutfamp[phase],pVal1,sizeof(uint32_t)*HARM_SIZE);
    float fScale = 1.0 * TypeMinMax<int32_t>::MAX_VAL / m_harmpeak[phase];
    float pScaleRatio[HARM_SIZE] = {0};
     // int16_t* wavedata = (int16_t*)pWave->GetData();
    int moveindex = 1;
    for ( uint32_t j = 0; j < HARM_SIZE; ++j )
    {
      //if(j==HzRadio[dts_fileno[phase]-1][moveindex-1])
      {
        pScaleRatio[moveindex] =m_harmoutfamp[phase][moveindex]*fScale; //harm_rate[phase][j] * fScale;
        _DBG_("m_harmoutfamp[phase]=%f",m_harmoutfamp[phase][moveindex]);
        moveindex++;
      }
    }
    float valuefval = 0;
    mm->SetParam( phase, pVal2,pVal3,
        pScaleRatio ,false,valuefval);//GetParamValue(phase+PMID_APHASE_OUT_DEGREE)->fval
    mm->GenWave();

    return mm;
  };
  virtual bool Check(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, bool flag, float value)
  {
    return true;
  };
//private:
//  float m_harmoutfamp[PWR_PHASE_QTY][HARM_SIZE];
//  float m_harmpeak[PWR_PHASE_QTY];
};

class Rand : public BuiltWave
{
public:
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, bool flag, float value)
  {
    //处理数据检查计算
    int phase = nVal;
    if (!Check(nVal, pVal1, pVal2, pVal3, flag, value))
      return NULL;
    WaveBase<int32_t, WAVE_DISP_POINTS>* mm = CreateWave<int32_t, WAVE_DISP_POINTS>(WAVE_TYPE_RAND);
    if(CheckWaveParam(nVal, pVal1, pVal2, pVal3, flag, value,m_harmpeak[phase]))
    {
      float fScale = 1.0 * TypeMinMax<int32_t>::MAX_VAL / m_harmpeak[phase];
      _DBG_("fScale=%f",fScale);
      fScale = fScale* (*(float*)pVal1);//GetParamValue(PMID_WAVE_PARAM1)->fval;
      _DBG_("fScale=%f",fScale);
      mm->SetParam( phase, &fScale,NULL,NULL ,false,1);
      mm->GenWave();
      return mm;
    }
    delete mm;
    return NULL;
  };
  virtual bool Check(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, bool flag, float value)
  {
    return true;
  };
//private:
//  float m_harmpeak[PWR_PHASE_QTY];
};

class Harm : public BuiltWave
{
public:
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, bool flag, float value)
  {
    //处理数据检查计算
    int phase = nVal;
    if (!Check(nVal, pVal1, pVal2, pVal3, flag, value))
      return NULL;
    WaveBase<int32_t, WAVE_DISP_POINTS>* mm = CreateWave<int32_t, WAVE_DISP_POINTS>(WAVE_TYPE_HARM);
    if(CheckWaveParam(HARM_SIZE, pVal1, pVal2, pVal3, flag, value,m_harmpeak[phase]))
    {
      memcpy(m_harmoutfamp[phase],pVal1,sizeof(uint32_t)*HARM_SIZE);
      float fScale = 1.0 * TypeMinMax<int32_t>::MAX_VAL / m_harmpeak[phase];
      float pScaleRatio[HARM_SIZE]={0};
      for ( uint32_t j = 0; j < HARM_SIZE; ++j )
      {
        pScaleRatio[j] =m_harmoutfamp[phase][j]*fScale; //harm_rate[phase][j] * fScale;  //
        _DBG_("pScaleRatio[j]=%f",pScaleRatio[j]);
      }
      mm->SetParam( HARM_SIZE, pScaleRatio, pVal2 ,harm_Hz,false,value);
      mm->GenWave();
      return mm;
    }
    delete mm;
    return NULL;
  };
  virtual bool Check(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3, bool flag, float value)
  {
    return true;
  };
};
#endif

class CreateFactory
{
public:
  CreateFactory(BASE_WAVE_TYPE type):m_type(type) {
    mwave = nullptr;
  };CreateFactory() {
    mwave = nullptr;
  };
  virtual ~CreateFactory()
  {
    delete mwave;
  }
  virtual WaveBase<int32_t, WAVE_DISP_POINTS>* build(uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value)
  {
    if(mwave == nullptr)
      mwave = dobuild();
    return mwave->build(nVal, pVal1,  pVal2, pVal3,pVal4, pVal5, pVal6,flag,value);
  }
protected:
  virtual BuiltWave * dobuild() = 0;
  BASE_WAVE_TYPE m_type;
private :
  BuiltWave *mwave;
};

class CommonFactory : public CreateFactory
{
public:
  CommonFactory(BASE_WAVE_TYPE type){m_type=type;}
protected:
  virtual BuiltWave * dobuild()
  {
    BuiltWave * mwave = new CommonWave(m_type);
    return mwave;
  };
};
#if MUTIL_WAVE
class HarmFactory : public CreateFactory
{
protected:
  virtual BuiltWave * dobuild()
  {
    BuiltWave * mwave = new Harm();
    return mwave;
  };
};
class HarmReverFactory : public CreateFactory
{
protected:
  virtual BuiltWave * dobuild()
  {
    BuiltWave * mwave = new Harm();
    return mwave;
  };
};

//以下几个波形需要独立可以操作

class ModulaFactory : public CreateFactory
{
protected:
  virtual BuiltWave * dobuild()
  {
    BuiltWave * mwave = new CommonWave(WAVE_TYPE_MODULA);
    return mwave;
  };
};

class DtsFactory : public CreateFactory
{
protected:
  virtual BuiltWave * dobuild()
  {
    BuiltWave * mwave = new Dts();
    return mwave;
  };
};
#endif
class ProductWave
{
public:
  ProductWave()
  {
  }
  const WaveBase<int32_t, WAVE_DISP_POINTS>* GetWave(BASE_WAVE_TYPE type, PWR_PHASE_NO phase = PWR_PHASE_A) { return m_pWave[type]; }
  virtual const bool SetParamToGenWave(BASE_WAVE_TYPE type, PWR_PHASE_NO phase,uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value)
  {
    bool Ret = false;
    WaveBase<int32_t, WAVE_DISP_POINTS>* wave = NULL;
    switch (type)
    {
#if MUTIL_WAVE
    case WAVE_TYPE_MODULA:
      fac = new ModulaFactory();
      wave = fac->build(nVal, pVal1, pVal2, pVal3, flag, value);
      break;
    case WAVE_TYPE_DTS:
      fac = new DtsFactory();
      wave = fac->build(nVal, pVal1, pVal2, pVal3, flag, value);
      break;

    case WAVE_TYPE_HARM:
      //计算
      fac = new HarmFactory();
      wave = fac->build(nVal, pVal1, pVal2, pVal3, flag, value);
      break;
#endif
    default:
      fac = new CommonFactory(type);
      wave = fac->build(nVal, pVal1, pVal2, pVal3,pVal4, pVal5,pVal6 ,flag, value);
      break;
    }
    if (wave)
    {
      delete m_pWave[type];
      m_pWave[type] = wave;
      m_type = type;
      Ret = true;
    }
    return Ret;
  }
  virtual BASE_WAVE_TYPE GetWaveType(PWR_PHASE_NO phase) { return m_type; };
  virtual void SetWaveType(BASE_WAVE_TYPE type, PWR_PHASE_NO phase) {
    m_type = type;
  };
protected:
  WaveBase<int32_t, WAVE_DISP_POINTS>* m_pWave[WAVE_TYPE_QTY];
  CreateFactory* fac;
  BASE_WAVE_TYPE m_type;

};

class ReverProductWave : public ProductWave
{
public:
  ReverProductWave(){};
  virtual const bool SetParamToGenWave(BASE_WAVE_TYPE type, PWR_PHASE_NO phase, uint32_t nVal, const void* pVal1, const void* pVal2, const void* pVal3,const void* pVal4, const void* pVal5,const void* pVal6, bool flag, float value)
  {
    bool Ret = false;
    WaveBase<int32_t, WAVE_DISP_POINTS>* wave = NULL;
    switch (type)
    {
#if MUTIL_WAVE
    case WAVE_TYPE_MODULA:
      fac = new ModulaFactory();
      wave = fac->build(nVal, pVal1, pVal2, pVal3, flag, value);
      break;
    case WAVE_TYPE_DTS:
      fac = new DtsFactory();
      wave = fac->build(nVal, pVal1, pVal2, pVal3, flag, value);
      break;

    case WAVE_TYPE_HARM:
      //计算
      fac = new HarmFactory();
      wave = fac->build(nVal, pVal1, pVal2, pVal3, flag, value);
      break;
#endif
    default:
      fac = new CommonFactory(type);
      wave = fac->build(nVal, pVal1, pVal2, pVal3,pVal4, pVal5, pVal6,flag, value);
      break;
    }
    if (wave)
    {
      delete m_pWave[type];
      m_pWave[type] = wave;
      m_type = type;
      Ret = true;
    }
    return Ret;
  }
private:
};

#define LENTH (51)
#define HarmScale(e,vac) ((e==HARMONIC_UINT_REAL)?(1):((e==HARMONIC_UINT_RATE)?100/vac:(1.414) ))

class WaveCtrl
{
public:
  WaveCtrl(){
    m_VoltWaveLib = new ProductWave();
    m_CurrWaveLib = new ProductWave();
    m_ListVoltWaveLib = new ProductWave();
    m_ListCurrWaveLib = new ProductWave();
  };
  static WaveCtrl* GetInstance()
  {
    static WaveCtrl* g_pCtrlParam = NULL;
    if (g_pCtrlParam == NULL)
      g_pCtrlParam = new WaveCtrl();
    return g_pCtrlParam;
  }
  ProductWave* GetWaveLib(WAVE_LIB_TYPE type,DDS_RUN_MODE mode=RUN_NORM_MODE )
  {
    return (mode == RUN_NORM_MODE)?((type==WAVE_LIB_VOLT)?m_VoltWaveLib:m_CurrWaveLib):
        ((type==WAVE_LIB_VOLT)?m_ListVoltWaveLib:m_ListCurrWaveLib);
  }

private:
  ProductWave* m_VoltWaveLib;
  ProductWave* m_CurrWaveLib;
  ProductWave* m_ListVoltWaveLib;
  ProductWave* m_ListCurrWaveLib;
};

#endif



总结:

  • 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
  • 抽象工厂模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
  • 抽象工厂模式适用情况包括:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;系统中有多于一个的产品族,而每次只使用其中某一产品族;属于同一个产品族的产品将在一起使用;系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

四:建造者模式

通过管理和组装各种部件,形成一个总体,复杂的对象。🚕和各种部件的关系

建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节

定义:造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节(可以通过用户指定不同的构建对象类型和内容创建一个对象返回,如何指定?用户就通过建造者对象指针传递进去/指定建造者建造,返回则是返回建造者建造完成的产品的对象指针,产品提供使用价值接口,用户直接使用产品价值接口)。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式

  • Builder:抽象建造者 (组合在指挥者内部)
  • ConcreteBuilder:具体建造者 (每一个构建者,实现自身需要创建的产品角色)
  • Director:指挥者 (用户调用的对象接口,传递建造者对象指针)
  • Product:产品角色 (由具体建造者构建,内部包含所需生产的各部件对象)
class PartA {
public:
    PartA() {
        name = "parta";
    };
    virtual void Set(std::string str) {
        name = str;  //部件的生产才知道部件的生产过程
    }; 
    virtual void show() { printf("%s\n", name.c_str()); };  //对外展示的接口
private:
    std::string name;
};
class PartB {
public:
    PartB() {};
    virtual void Set() {};
    virtual void show() { printf("PartB\n"); };
};

class prod {
public:
    prod(PartA* PartA,PartB* PartB) {  //产品应该可以知道该产品包含哪些组成部分
        m_PartA = PartA;
        m_PartB = PartB;
    };
    virtual void show() {  //用户使用产品的接口,里面就有部件的工作流程
        if(m_PartA)
            m_PartA->show();
        if(m_PartB)
            m_PartB->show();
    };
    PartA* m_PartA;   
    PartB* m_PartB;
};


class builder { //管理不同建造者
public:
    builder() {};

    virtual void builderPartA() {};
    virtual void builderPartB() {};
    virtual void builderPartC() {};
    virtual prod* getresult() { return m_prod;};

protected:
    prod* m_prod;  //让不同的建造者生产不同的产品
};

class cretebuilder:public builder {  //特定的prod产品生产者
public:
    cretebuilder() {
        m_prod = new prod(new PartA(),new PartB());
    };
private:
    virtual void builderPartA() {
        m_prod->m_PartA->Set("set ty A");  //部件的创建属性使用哪种操作应该由建造者知道,
                                 //部件属性的构建流程和生产需要的物品应该是由生产部件的才知道,
                                 //但是会提供接口给外部使用
    };
    virtual void builderPartB() {
        m_prod->m_PartB->Set();
    };
    virtual void builderPartC() {};
    virtual prod* getresult() {  //子类不更改行为,可以直接放builder中
        return m_prod;
    };
    //   prod* constuct()//其实这个也可以在这里,用户直接调用,就表明用户直接指定某一个建造者组件产品。
};

class Director {  //代理用户指挥建造者建造产品
public:
    Director() {
    }

    ~Director() {
    }

    prod* constuct() {  //建造者需要跟指挥人汇报的进度和建造部件。理论上这里应该拆成两部分
        //部件的进度不在这里调用,这里应该只调用m_pbuilder->getresult();
        m_pbuilder->builderPartA();
        m_pbuilder->builderPartB();
        m_pbuilder->builderPartC();

        return m_pbuilder->getresult();
    }

    void setBuilder(builder* buider) {
        m_pbuilder = buider;
    }
    builder * m_pbuilder;
};

int main(int argc, char* argv[])
{
    cretebuilder* buildefr = new cretebuilder();
    Director  director; //用户呼叫代理者建造产品
    director.setBuilder(buildefr);  //代理指定产品建造人建造
    prod* pd = director.constuct();  //用户代理人交给用户东西
    pd->show(); //用户使用产品

    delete buildefr;
    delete pd;
    return 0;
}
//注意:也就是说,建造者模式,指挥者不是必须的,但是有指挥者就可以实现相同的创建过程,可以创建不同的产品对象
//通过传递给指挥者不同的建造者模式。

抽象建造者类中定义了产品的创建方法和返回方法;

建造者模式的结构中还引入了一个指挥者类Director,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象

在客户端代码中,无须关心产品对象的具体组装过程,只需确定具体建造者的类型即可,建造者模式将复杂对象的构建与对象的表现分离开来,这样使得同样的构建过程可以创建出不同的表现

优点:

  • 在建造者模式中, 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象**(通过指挥者传递不同建造者实现不用产品)。**
  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 (每一种建造者建造不相同的产品)。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”****。

缺点:

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂(组成部分和创建产品的流程复杂多样,那么上述的指挥者的constuct里面的partA,partB,partC不一定能满足、或者建造者的partA,partB,partC流程不一定满足),可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大

在以下情况下可以使用建造者模式:

  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
  • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  • 对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类中。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品

扩展:

建造者模式的简化:

  • 省略抽象建造者角色:如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。
  • 省略指挥者角色:在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略指挥者角色,让Builder角色扮演指挥者与建造者双重角色

对比抽象工厂:

  • 抽象工厂产生的是一系列产品,构成一个产品族,建造者模式只是生产特定组装完整的一个产品。侧重于一步步构造一个复杂对象,返回一个完整的对象
  • 如果将抽象工厂模式看成 汽车配件生产工厂 ,生产一个产品族的产品,那么建造者模式就是一个 汽车组装工厂 ,通过对部件的组装可以返回一辆完整的汽车

总结:

  • 建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。
  • 建造者模式包含如下四个角色:抽象建造者为创建一个产品对象的各个部件指定抽象接口;具体建造者实现了抽象建造者接口,实现各个部件的构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象;产品角色是被构建的复杂对象,包含多个组成部件;指挥者负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造
  • 在建造者模式的结构中引入了一个指挥者类,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。
  • 建造者模式的主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”,还可以更加精细地控制产品的创建过程;其主要缺点在于由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
  • 建造者模式适用情况包括:需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性;需要生成的产品对象的属性相互依赖,需要指定其生成顺序;对象的创建过程独立于创建该对象的类(上述prod产品的创建过程和返回该对象的接口);隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同类型的产品。

五:单例模式

定义:确保整个类只有一个实例,并由类自身管理和向系统提供该实例,提供全局访问接口

  • Singleton:单例
class singleton {
public:
    static singleton* psingleton;
    static singleton* getInstance() {
        if (psingleton == nullptr)   //简单的single模式,线程不安全。如果嫌线程安全
        {                            //须在判断之前加锁,申请完后释放     
            psingleton = new singleton();
        }
        return psingleton;
    };

    void singletonOperation() {
        cout << "singletonOperation" << endl;
    }

private:
    singleton(){};
};

singleton* singleton::psingleton = nullptr;
int main(int argc, char* argv[])
{
    singleton* sg = singleton::getInstance();
    sg->singletonOperation();

    return 0;
}
  • 单例类的构造函数为私有;
  • 提供一个自身的静态私有成员变量;
  • 提供一个公有的静态工厂方法。

优点:

  • 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
  • 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。

缺点:

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失

结构型模式:

原理:将类和对象通过组合在一起实现复杂,庞大的结构

类结构型模式:关心类的组合,由多个类可以组合成一个更大的系统。一般只存在继承关系和实现关系。

对象结构型模式:关心类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法

根据“合成复用原则”,在系统中尽量使用关联关系来替代继 承关系,因此大部分结构型模式都是对象结构型模式

一:适配器模式

将原先设计有的一个类的功能,使用适配器包含该现有的类对象,提供客户需要的接口供访问,该接口的内部实现实际上是调用现有类的功能接口。以达到用户定义的接口实现现有的功能。

  • 软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。
  • 通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。
  • 在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
  • 在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者(Adaptee),即被适配的类(当前系统已存在的类)。
  • 适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器(Adapter)的方法时,在适配器类的内部将调用适配者类(Adaptee)的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。
  • Target:目标抽象类
  • Adapter:适配器类
  • Adaptee:适配者类
  • Client:客户类
//对象结构型模式:
class Adaptee {  //系统已存在的类
public:
    Adaptee() {};
    virtual void Gotan()
    {
        printf("adaptee func\n");
    }
};

class Target  //客户需要的类接口
{
public:
    Target() {};
    virtual int ClentFunc()=0;
};
//对象结构型模式,可以件多个适配者类集合在一起,实现用户需求接口
class Adapter :public Target {  //适配器继承客户需要的类并调用Adaptee的接口实现客户需要的接口
public:
    Adapter(Adaptee* pAdapter) {  //通过调用者指定想要适配哪一个适配者类
        m_Adaptee = pAdapter;
    };
    virtual int ClentFunc()
    {
        m_Adaptee->Gotan();
        return 1;
    };
private:
    Adaptee *m_Adaptee;
};

//类结构型模式:  //可以通过适配器更改适配者模式的接口,实现不同于适配者类的方法
class Adapterl :public Target, public Adaptee {  //适配器继承客户需要的类和Adaptee的接口类
public:
    Adapterl() {  

    };
    virtual int ClentFunc()
    {
        Adaptee::Gotan();
        return 1;
    };
private:
};

int main(int argc, char* argv[])  //客户类
{
    Adaptee* adaptee = new Adaptee();
    Target* tar = new Adapter(adaptee);  //对象结构型模式
    tar->ClentFunc();

    return 0;
}

优点:

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

类适配器模式还具有如下优点:

由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还具有如下优点:

一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口

缺点:

类适配器模式的缺点如下:

对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。

对象适配器模式的缺点如下:

与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂

扩展:

认适配器模式(Default Adapter Pattern)或缺省适配器模式

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式

二:桥接模式

针对两个不同变化维度进行组合形成一个系统。比如形状和颜色。

桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量

定义:

将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式

  • Abstraction:抽象类 (一个/可以多个,包含实现类对象指针)
  • RefinedAbstraction:扩充抽象类 (可以多个,实现抽象类中针对具体实现的组合调用接口)
  • Implementor:实现类接口 (一个维度一个)
  • ConcreteImplementor:具体实现类 (一个维度多个)
//如果实现和抽象不分离,则是Abstion 继承Implementor 
//在Abstion 直接实现operation,调用ImplementorA 等接口

class Implementor {   //定义某一种具体实现的抽象类
public:
    Implementor() {};
    virtual int operat() {};

};

class ImplementorA :public Implementor {    //具体实现
public: 
    ImplementorA() {
    };
    virtual int operat() {
        printf("Imp A\n");
    };
};

class ImplementorB :public Implementor {
public:
    ImplementorB() {
    };
    virtual int operat() {
        printf("Imp A\n");
    };
};

class Impbug {   //定义另一种具体实现的抽象类,这个可以不需要
public:
    Impbug() {};
    virtual int operat() {};
};

class ImpbugB :public Impbug {
public:
    ImpbugB() {
    };
    virtual int operat() {
        printf("ImpbugB B\n");
    };
};

class Abstion {  //之所以添加一个抽象,是为了实现operation的不同,形成不同的抽象具体类:
                 //比如两个维度的变化,在operation里面的调用顺序不同
                 //父类:也是为了管理子类相同的部分,提取放置在父类中
public:
    Abstion(Implementor* imp, Impbug* pImpbug) {  //当有多种不同维度的具体实现时,这个接口传递进去两个维度的指针
        mImp = imp;
        m_Impbug = pImpbug;
    };
    virtual int operation()=0 ;  //子类实现的接口,调用两个维度自身的操作
protected:
    Implementor* mImp;           //包含两个维度的对像指针
    Impbug* m_Impbug;
}; 
//ReAbstion 可以认为是形状的具体类,比如三角,Implementor可以认为是颜色的父类,通过外部传递的不同颜色,形成不同颜色三角形
class ReAbstion :public Abstion {  //不同的ReAbstion,可以实现不同的operation
public:
    ReAbstion(Implementor* imp, Impbug* pImpbug):Abstion(imp, pImpbug) {
        
    };
    virtual int operation() {  //通过外部传递的不同维度的指针实现的接口。这种是将两种变化组合了
        mImp->operat();   
        m_Impbug->operat();
    };
};

int main(int argc, char* argv[])
{
    Implementor* pImp = new ImplementorA();
    Impbug* pImpbug = new ImpbugB();
    Abstion* pa = new ReAbstion(pImp, pImpbug);  //将抽象和实现分离,并实现多个维度变化的组合。
    pa->operation();

    Abstion* pb = new ReAbstion(new ImplementorB(), pImpbug);
    pb->operation();

    delete pa;
    delete pb;
    return 0;
}
//理解:ReAbstion 可以认为是形状的具体类,比如三角,Implementor可以认为是颜色的父类,通过外部传递的不同颜色,形成不同颜色三角形
//重点:其实就是将两个不同维度变化的东西,将一种维度作为抽象类(形状)去实现,另一种维度作为实现类(颜色)去实现,
//抽象类里面包含由调用者传递的实现类的对象指针


分析:

理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化(其实就是将两个不同维度变化的东西,将一种维度作为抽象类(形状)去实现,另一种维度作为实现类(颜色)去实现,抽象类里面包含由调用者传递的实现类的对象指针)

桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

优点:

  • 分离抽象接口及其实现部分。
  • 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
  • 实现细节对客户透明,可以对用户隐藏实现细节

缺点:

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 - 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

在以下情况下可以使用桥接模式:

  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

扩展:

适配器模式与桥接模式的联用:

  • 桥接模式和适配器模式用于设计的不同阶段,桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式(针对的是具体实现类(或者具体抽象类)不满足用户提供的接口使用时,也就是说用户不想使用ImplementorA 里面的接口实现新功能/不满足新功能,可以添加一个适配器(Adapter)去包含ImplementorA ,完善一个接口:调用ImplementorA 的接口和添加新的行为)。但有时候在设计初期也需要考虑适配器模式,特别是那些涉及到大量第三方应用接口的情况

总结:

  • 桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
  • 桥接模式包含如下四个角色:抽象类中定义了一个实现类接口类型的对象并可以维护该对象;扩充抽象类扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法;实现类接口定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的操作;具体实现类实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象将替换其父类对象,提供给客户端具体的业务操作方法。
  • 在桥接模式中,抽象化(Abstraction)与实现化(Implementation)脱耦,它们可以沿着各自的维度独立变化。
  • 桥接模式的主要优点是分离抽象接口及其实现部分,是比多继承方案更好的解决方法,桥接模式还提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以对用户隐藏实现细节;其主要缺点是增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。
  • 桥接模式适用情况包括:需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系;抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响;一个类存在两个独立变化的维度,且这两个维度都需要进行扩展;设计要求需要独立管理抽象化角色和具体化角色;不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统。

三:装饰模式

目的:给一个类或是对象动态(运行时/用户调用时)的控制增加行为或改变属性

  • 继承机制:通过继承直接增加行为和属性,但这种是静态的,不方便管理和控制行为的增加方式和时机。
  • 关联机制:即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机

定义:

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式

  • Component: 抽象构件 (一个规矩的父类)
  • ConcreteComponent: 具体构件 (一个规矩的具体产品)
  • Decorator: 抽象装饰类 (一个能保存被装饰的产品的指针和调用行为)
  • ConcreteDecorator: 具体装饰类 (一个继承抽象装饰类,并可以添加行为的装饰器,需要传递进去待被装饰的产品的对象指针)
//装饰器,实现将具体产品指针,可同时指向具体装饰器,据图装饰器需要传入被装饰的产品对象指针
//同时抽象装饰类,具体装饰类都要有抽象构件的行为接口。
//从类继承结构看,Component是Decorator的父类,是ConcreteDecorator的超父类

class Component {
public:
    Component() {
        
    };
    virtual void Operator() = 0;
};

class CreteComponent : public Component
{
public:
    CreteComponent() {
       
    };
    virtual void Operator() {
        printf("pon\n");
    };
};

class Decorator: public Component  //设计一个装饰器,用于装饰特定的某一类产品
{
public:
    Decorator(Component* pon){  //装饰具体的产品
        m_pon = pon;   //抽象的装饰器需要保存原有产品的指针和行为
    };
    virtual ~Decorator()
    {
        delete m_pon;
    };
    virtual void Operator() {   //需要实现被装饰产品同样的的接口
        m_pon->Operator();   //可以实现原有产品行为
    }
protected:
    Component* m_pon;  //指向被装饰的产品
};

class DecoratorA :public Decorator {  //具体装饰器,实现需要装饰的特定行为和属性添加
public:
    DecoratorA(Component* pon):Decorator(pon) {};
    virtual void Operator() {
        Decorator::Operator();    //被装饰的产品的原有行为
        Addbehavior();  //添加行为
    }
    void Addbehavior() {  //这里也可以用于修改被装饰产品的属性
        printf("add dec\n");
    };
};


class DecoratorB :public Decorator {  //具体装饰器,实现需要装饰的特定行为和属性添加
public:
    DecoratorB(Component* pon) :Decorator(pon) {};
    virtual void Operator() {
        Decorator::Operator();
        Addbehavior();  //添加行为
    }
    void Addbehavior() {   //这里也可以用于修改被装饰产品的属性/读取属性再做改变
        printf("rec dec\n");
    };
};


int main(int argc, char* argv[])
{
    Component* ppon = new CreteComponent();  //创建产品
    ppon = new DecoratorA(ppon);  //使用装饰器将产品装饰起来,再返回给产品指针
                                  //装饰器重点在这个调用上
    ppon = new DecoratorB(ppon);  //多个装饰类装饰同一个产品
    ppon->Operator();
   
    delete ppon;
    return 0;
}

分析:

  • 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性,因此使得系统更加容易维护。当然,关联关系的缺点是比继承关系要创建更多的对象。
  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展

优点:

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。(动态改变:就是运行时,通过用户调用时传递和调用的顺序可以产生不同的行为属性)
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。(实现的具体装饰类可以向内传递更多种的具体产品类(具体构件类),然后引发更多的行为改变,丰富单一对象的行为模式)

缺点:

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐

在以下情况下可以使用装饰模式:

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长(组合产生的子类爆炸性成长:比如上面的桥接模式,当具体实现太多的时候,又想将多个实现组合在同一个对象中时,可以采用装饰器模式将此对象装饰,得到更多具体类的行为);第二类是因为类定义不能继承(如final类)

扩展:

  • 一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。
  • 尽量保持具体构件类ConcreteComponent作为一个“轻”类,也就是说不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类对其进行扩展。
  • 如果只有一个具体构件类(ConcreteComponent)而没有抽象构件类(Component),那么抽象装饰类(Decorator)可以作为具体构件类(ConcreteComponent)的直接子类

总结:

  • 装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功 能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模 式。
  • 装饰模式包含四个角色:抽象构件定义了对象的接口,可以给这些对 象动态增加职责(方法);具体构件定义了具体的构件对象,实现了 在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法); 抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具 体职责在其子类中实现;具体装饰类是抽象装饰类的子类,负责向构 件添加新的职责。
  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动 态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子 类的情况下,将对象的功能加以扩展。
  • 装饰模式的主要优点在于可以提供比继承更多的灵活性,可以通过一种动态的 方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的 排列组合,可以创造出很多不同行为的组合,而且具体构件类与具体装饰类可 以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类;其主要缺 点在于使用装饰模式进行系统设计时将产生很多小对象,而且装饰模式比继承 更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需 要逐级排查,较为烦琐。
  • 装饰模式适用情况包括:在不影响其他对象的情况下,以动态、透明的方式给 单个对象添加职责;需要动态地给一个对象增加功能,这些功能也可以动态地 被撤销;当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展 和维护时。
  • 装饰模式可分为透明装饰模式和半透明装饰模式:在透明装饰模式中,要求客 户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构 件类型和具体装饰类型,而应该全部声明为抽象构件类型;半透明装饰模式允 许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。

四:外观模式

定义:外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式(使用外观对象作为一个提供外部访问的类对象,通过这个对象间接访问到子系统功能)

  • Facade: 外观角色
  • SubSystem:子系统角色
class SystemA {
public: 
    SystemA() {
    };

    void operation() {
        printf("A\n");
    };
private:
};

class SystemB {
public:
    SystemB() {
    };

    void operation() {
        printf("B\n");
    };
private:
};

class Facade {   //外观对象类
public:
    Facade() {
        m_A = new SystemA();
        m_B = new SystemB();
    };
    virtual void dosystem() { //提供访问子系统的接口
        m_A->operation();
        m_B->operation();
    };
private:
    SystemA* m_A;
    SystemB* m_B;
};

int main(int argc, char* argv[])
{
    Facade fa;  //(非抽象外观类)这种方式只能单一支持当前子系统行为,使用客户端调用抽象外观类,可以丰富子系统行为。
    fa.dosystem();

    return 0;
}

分析:外观模式可以使得子系统之间的通信和相互依赖关系达到最小。使得客户端只需要跟外观对象打交道就可以控制子系统工作

根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。 -外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。 - 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。 -外观模式的目的在于降低系统的复杂程度。 -外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能

优点:

  • 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
  • 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
  • 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
  • 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类

缺点:

  • 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
  • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”

扩展:

一个系统有多个外观类

在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。在很多情况下为了节约系统资源,一般将外观类设计为单例类。当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能。

不要试图通过外观类为子系统增加新行为

不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的(注意这点,经常会陷入这种错误做法)。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。

外观模式与迪米特法则

外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少,使得客户端与子系统内部的对象的相互作用被外观对象所取代。外观类充当了客户类与子系统类之间的“第三者”,降低了客户类与子系统类之间的耦合度,外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器。

抽象外观类的引入

外观模式最大的缺点在于违背了“开闭原则”,当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。(通过增加一个抽象外观类给客户端调用,具体外观类实例化操作子系统,以此增加子系统新行为)

五:享元模式

动机:抽象出相同的内容(一般是对象粒度比较小)进行共享,以此减少系统太多类的情况,不同的由外部设置。

  • 享元模式正是为解决这一类问题而诞生的。享元模式通过共享技术实现相同或相似对象的重用。
  • 在享元模式中可以共享的相同内容称为内部状态(IntrinsicState),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
  • 在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。
  • 在享元模式中共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象。享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。

定义:

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用(通过链表等保存生成的对象,在后续使用中,如果该对象符合使用要求,就返回给对象指针,如果不满足就生成多一个对象并放置在链表、map等)。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式

  • Flyweight: 抽象享元类 (一个,作为共享和不共享内容的父类)
  • ConcreteFlyweight: 具体享元类 (多个/一个,就是享元对象)
  • UnsharedConcreteFlyweight: 非共享具体享元类 (多个/一个)
  • FlyweightFactory: 享元工厂类 (一个,提供客户端调用的接口得以使用共享内容,以及记录储存共享内容)
//主要就是解决多细粒度子类的复用,核心就是享元工厂类的实现
class ShareAbs {
public:
    ShareAbs() {};
    virtual void Operation() {};  //主要提供接口针对外部状态的操作和传递
protected:
    std::string state;
};

class ShareInponet:public ShareAbs{ //共享的对象:享元对象
public:
    ShareInponet(std::string str) {
        state = str;
    };
    virtual void Operation() {
        printf("Inponet %s\n", state);
    };
private:
    
};

class ShareNoInponet :public ShareAbs {   //不共享的对象,继承抽象共享实现不同操作,客户端调用
public:
    ShareNoInponet(std::string str,int inx) {
        state = str;
        nstate = inx;
    };
    virtual void Operation() {
        printf("NoInponet %s\n", state);
    };
private:
    int nstate;
};

#include "map"
class ShareFac  //抽象工厂是提供给外部客户端调用,应该设计为单例
{
public:
    ShareFac() {};
    ~ShareFac() {};

    virtual ShareAbs* DoShareWish(std::string str)
    {
        map<string, ShareAbs*>::iterator itr = m_map.find(str);
        if (itr == m_map.end())  //查询保存的共享对象是否满足使用要求,满足直接返回,不满足,创建新共享对象并记录
        {
            ShareAbs* fw = new ShareInponet(str);  //创建享元对象
            m_map.insert(make_pair(str, fw));
            return fw;
        }
        else
        {
            cout << "aready in the pool,use the exist one:" << endl;
            return itr->second;
        }
    };
private:
    std::map<std::string, ShareAbs*> m_map;  //创建共享池记录可以被共享复用的对象和状态等
};

int main(int argc, char* argv[])
{
    ShareFac factory;
    ShareAbs* fw = factory.DoShareWish("one");//外部可以将获得的共享内容传递给不共享内容,以此得到享元对象的完整状态处理。
    fw->Operation();  //操作享元对象,可以往里传递享元对象的外部状态

    ShareAbs* fw2 = factory.DoShareWish("two");
    fw2->Operation();
    //aready exist in pool
    ShareAbs* fw3 = factory.DoShareWish("one");
    fw3->Operation();
    return 0;
}

分析:

享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。

享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。(核心在享元工厂的使用)

享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。

  • 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
  • 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。(相对于外部状态来说,客户端获得的内部状态,也是外部状态,因此状态之间是独立的)

优点:

  • 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

缺点:

  • 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

应用例子:

享元模式在编辑器软件中大量使用,如在一个文档中多次出现相同的图片,则只需要创建一个图片对象,通过在应用程序中设置该图片出现的位置,可以实现该图片在不同地方多次重复显示

扩展:

单纯享元模式和复合享元模式

  • 单纯享元模式:在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。
  • 复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。

享元模式与其他模式的联用

  • 在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。
  • 在一个系统中,通常只有唯一一个享元工厂,因此享元工厂类可以使用单例模式进行设计。
  • 享元模式可以结合组合模式形成复合享元模式,统一对享元对象设置外部状态。

总结:

  • 享元模式运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用,它是一种对象结构型模式。
  • 享元模式包含四个角色:抽象享元类声明一个接口,通过它可以接受并作用于外部状态;具体享元类实现了抽象享元接口,其实例称为享元对象;非共享具体享元是不能被共享的抽象享元类的子类;享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中。
  • 享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态和外部状态。其中内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享;外部状态是随环境改变而改变的、不可以共享的状态。
  • 享元模式主要优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份;其缺点是使得系统更加复杂,并且需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
  • 享元模式适用情况包括:一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费;对象的大部分状态都可以外部化,可以将这些外部状态传入对象中;多次重复使用享元对象

六:代理模式

代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。(可以控制客户端对目标对象的访问权限)

通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机

定义:

代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。

  • Subject: 抽象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 真实主题角色
class subject {
public:
    subject() {
    };
    virtual void request();
};

class realsub :public subject{
public:
    realsub() {};
    virtual void request() {  
        printf("real request\n");
    }

};

class Proxysub : public subject{ //特定代理某一种
public:
    Proxysub() {
        m_sub = new realsub();  //如果这个是外部传入的,表示可以代理任意一种subject的类对象,有些像装饰模式
                                //装饰模式实现的是subject接口会返回subject类型对象指针的,代理不做返回
                                //代理就已经涵盖了realsub这个的调用,并控制realsub的调用
    };
    void prerequest() {
        printf("Proxysub request prev\n");
    }
    void afterrequest() {
        printf("Proxysub request after\n");
    }
    virtual void request() {
        prerequest();
        m_sub->request();
        afterrequest();
    }
    realsub* m_sub;
};

int main(int argc, char* argv[])
{
    Proxysub proxy;
    proxy.request();

    return 0;
}

优点:

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限

缺点:

  • 由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。

适用:

根据代理模式的使用目的,常见的代理模式有以下几种类型:

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地 的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 另一台主机中,远程代理又叫做大使(Ambassador)。
  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟 到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。🔒
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

应用:

EJB、Web Service等分布式技术都是代理模式的应用。在EJB中使用了RMI机制,远程服务器中的企业级Bean在本地有一个桩代理,客户端通过桩来调用远程对象中定义的方法,而无须直接与远程对象交互。在EJB的使用中需要提供一个公共的接口,客户端针对该接口进行编程,无须知道桩以及远程EJB的实现细节

扩展:

几种常用的代理模式

  • 图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制。用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览
  • 远程代理:远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的远程业务对象是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作。
  • 虚拟代理:当一个对象的加载十分耗费资源的时候,虚拟代理的优势就非常明显地体现出来了。虚拟代理模式是一种内存节省技术,那些占用大量内存或处理复杂的对象将推迟到使用它的时候才创建。在应用程序启动的时候,可以用代理对象代替真实对象初始化,节省了内存的占用,并大大加速了系统的启动时间。

动态代理 (重点理解)解决减少代理角色的个数

  • 动态代理是一种较为高级的代理模式,它的典型应用就是Spring AOP。
  • 在传统的代理模式中,客户端通过Proxy调用RealSubject类的request()方法,同时还在代理类中封装了其他方法(如preRequest()和postRequest()),可以处理一些其他问题。
  • 如果按照这种方法使用代理模式,那么真实主题角色必须是事先已经存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色,这将导致系统中的类个数急剧增加,因此需要想办法减少系统中类的个数,此外,如何在事先不知道真实主题角色的情况下使用代理主题角色,这都是动态代理需要解决的问题

总结:

在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。 - 代理模式包含三个角色:抽象主题角色声明了真实主题和代理主题的共同接口;代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象;真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方法。 - 代理模式的优点在于能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;其缺点在于由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象,它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。- 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。 - 保护代理可以控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

行为型模式:

主要是针对对象划分责任和算法的抽象化

类行为型模式:使用类之间的继承关系划分 对象行为型模式:对象之间采用聚合关联关系划分责任和算法

一:命令模式

主要实在设计对象之间交互的时候产生的请求解耦,彼此不需要知道彼此存在的必要性

定义:

命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式

  • Command: 抽象命令类 (一个)
  • ConcreteCommand: 具体命令类 (可以多个,每一种请求一个,包含和记录接收者句柄)
  • Invoker: 调用者 (可以多个,表明多种使用者,/同一发送者可调用不同指令请求)
  • Receiver: 接收者 (可以多个,表明多种接收者,/同一接收者可以响应不同命令操作)
  • Client:客户类

class Receiver {   //接收者可以多加一个抽象类,表明可以有多种接收命令者
public:
    Receiver() {

    };
    virtual void action(std::string sfd)
    {
        printf("receiver is %s\n",sfd.c_str());
    };
};

class Commond {
public:
    Commond() {
    };
    virtual void execute()
    {
    }
protected:
    Receiver* m_rec;   //为了发送者和客户端不具备知道接收者,可以把这个放置到具体指令中
};

class CreteCommond :public Commond{ //将每一种请求都抽象化为一种类。里面可以丰富多种接收者,丰富多个指令请求
public:
    CreteCommond(Receiver* rec){  //支持多种接收,或者组合调用多个命令给到多个接收者
        m_rec = rec;
    };
    virtual void execute()
    {
        printf("do commond\n");
        m_rec->action("CreteCommond");  //直接在指令里通知接收者,当前有此条指令请求。
    };

};

//类似宏命令??:
class HCommond :public Commond{ //将每一种请求都抽象化为一种类。里面可以丰富多种接收者,丰富多个指令请求
public:
    HCommond (Receiver* rec){  //支持多种接收,或者组合调用多个命令给到多个接收者
        m_rec = rec;
    };
    virtual void execute()
    {
        printf("do commond\n");
        m_rec->action("CreteCommond");  //直接在指令里通知接收者,当前有此条指令请求。
        //或者这里的m_rec 实例化其他接收者对象
        CreteCommond *crm = new CreteCommond(m_rec);
        crm->execute();
    };
};

class Invoker {
public:
    Invoker(Commond* com) {  //可以传递命令数组
        m_com = com;
    };
    virtual void call()
    {
        m_com->execute();
    }

    virtual void callexe(Commond* com) //调用者调用某个指令接口
    {
        com->execute();  //调用者直接发送指令
    }
private:
    Commond* m_com;
};

int main(int argc, char* argv[])
{
    Receiver* pReceiver = new Receiver();
    CreteCommond* pCommand = new CreteCommond(pReceiver);  //通过命令记录和通知某一种命令接收者
    Invoker* pInvoker = new Invoker(pCommand);  //通过传递命令,告知接收者什么请求。
    pInvoker->call();

    delete pReceiver;
    delete pCommand;
    delete pInvoker;
    return 0;
}

分析:

命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。

  • 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
  • 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
  • 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
  • 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联(只有具体实现命令跟接收者关联)

实例应用:

电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例

优点:

  • 降低系统的耦合度。
  • 新的命令可以很容易地加入到系统中。
  • 可以比较容易地设计一个命令队列和宏命令(组合命令)。
  • 可以方便地实现对请求的Undo和Redo。

缺点:

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用

适用:

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。(怎么理解这个?是可以通过命令对象恢复接收者的状态等?)
  • 系统需要将一组操作组合在一起,即支持宏命令

应用:

很多系统都提供了宏命令功能,如UNIX平台下的Shell编程,可以将多条命令封装在一个命令对象中,只需要一条简单的命令即可执行一个命令序列(即通过一个命令对象,包含多个具体实际命令动作和通知),这也是命令模式的应用实例之一

扩展:宏命令实现????(命令里面其他包含命令对象,使用同一个接收者对象指针?)

宏命令又称为组合命令,它是命令模式和组合模式联用的产物。

-宏命令也是一个具体命令,不过它包含了对其他命令对象的引用,在调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员对象可以是简单命令,还可以继续是宏命令。执行一个宏命令将执行多个具体命令,从而实现对命令的批处理

二:中介者模式

目的:将原本对象之间存在复杂的相互引用的关系,变为松耦合关系。使得彼此可以独立变化。(将类之间的组合关联关系,通过引入中介者进行解耦)

定义:

中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式

  • Mediator: 抽象中介者 (可以不存在,抽象中介,表明可以让角色跟多种中介者交互)
  • ConcreteMediator: 具体中介者 (实现两个角色之间的交互,解耦角色)
  • Colleague: 抽象同事类 (将两个需要交互的角色抽象同一个父类)
  • ConcreteColleague: 具体同事类 (具体交互的角色,需要知道代交互的中介者)
class Mediator;
class Collge {
public:
    Collge() {};

    virtual void Sendmsg(int own,std::string msg)
    {
    };
    virtual void SetMeditor(Mediator* medi)
    {
        m_medi = medi;
    }
    virtual void Recvmsg(int own, std::string msg)
    {
    }

protected:
    Mediator *m_medi;
    int m_own;
};

//A ,B可以是不同父类两个类之间的交互,中介者起到可以记录两者之间的句柄,通过查询传递的角色,调用角色交互
//中介者必须贯穿在两个角色之间,即两个角色都可以访问到同一中介者(起到交互角色之间的中转和传递功能)

class CollgeA :public Collge {
public:
    CollgeA() {
        m_own = 1;
    };

    virtual void Sendmsg(int own, std::string msg)
    {
        printf("A send %s to %d\n", msg.c_str(), own);
        m_medi->operation(m_own,own, msg);    //调用中介者,发送通知,接收者由中介去通知,角色彼此不需要知道,
                                              //没有直接交互关系 ,将两者解耦
    };
    virtual void Recvmsg(int own, std::string msg) //在有权限管理的角色中,接收和发送接口可以多种多样
    {
        printf("A recv %s from %d\n", msg,own);
    }
};


class CollgeB :public Collge {
public:
    CollgeB() {
        m_own = 2;
    };

    virtual void Sendmsg(int own, std::string msg)
    {
        printf("B send %s to %d\n", msg.c_str(), own);
        m_medi->operation(m_own, own, msg);
    };
    virtual void Recvmsg(int own, std::string msg)
    {
        printf("B recv %s from %d\n", msg, own);
    }
};


class Mediator {
public:
    Mediator(){

    };
    virtual void operation(int src, int des, std::string msg)
    {
    };
    virtual void regist(int other, Collge* pcollge){
    
    };
};

class CreMediator:public Mediator {
public:
    CreMediator() {

    };
    virtual void regist(int other, Collge* pcollge) {
        map<int, Collge*>::const_iterator itr = m_map.find(other);
        if (itr == m_map.end())
        {
            m_map.insert(make_pair(other, pcollge));
            //同时将中介类暴露给colleague 
            pcollge->SetMeditor(this);  //重点在这里,让需要交互的角色知道自己需要跟哪个中介者通信
                                        //或者将这里放到角色中去,表明角色跟哪个中介通信是自己已经知道的
                                        //即在角色创建的时候传递一个中介者进去,但是这种情况,表明这个角色创建之后就只能跟这个中介者通信了
                                        //或者将这个接口在SetMeditor角色中体现,创建的时候,客户端调用一遍这个。
        }
    };
    virtual void operation(int src,int des,std::string msg)
    {
        map<int, Collge*>::const_iterator itr = m_map.find(des);
        if (itr == m_map.end())
        {
            cout << "not found this colleague!" << endl;
            return;
        }
        //在这里可以针对msg进行过滤,表明有些msg不能发送出去
        Collge* pc = itr->second;
        pc->Recvmsg(src, msg);
    };
private:
    std::map<int, Collge*> m_map;//为了管理多个交互的角色,采用map记录句柄
};


int main(int argc, char* argv[])
{
    Mediator* pm = new CreMediator();
    CollgeA* pa = new CollgeA();
    CollgeB* pb = new CollgeB();
    //pa->SetMeditor(pm);  //直接放置在这里也可
    pm->regist(1, pa);   //告知中介者要负责交互的角色
    pm->regist(2, pb);

    // sendmsg from a to b
    pa->Sendmsg(2, "hello,i am a");
    // sendmsg from b to a
    pb->Sendmsg(1, "hello,i am b");

    delete pa, pb, pm;
    return 0;
}



分析:

  • 中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,通过中介者即可。该中转作用属于中介者在结构上的支持。
  • 协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持

实例应用:

某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该聊天室进行信息交流,普通会员(CommonMember)可以给其他会员发送文本信息(说明有接受图片和文本信息的接口),钻石会员(DiamondMember)既可以给其他会员发送文本信息,还可以发送图片信息(说明钻石会员没有接收图片信息的接口)。该聊天室可以对不雅字符进行过滤,如“日”等字符;还可以对发送的图片大小进行控制(表明具体中介者需要可以针对信息进行过滤和判断,有筛选信息能力)。用中介者模式设计该虚拟聊天室。

优点:

  • 简化了对象之间的交互。
  • 将各同事解耦。
  • 减少子类生成。
  • 可以简化各同事类的设计和实现

缺点:

  • 在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

适用:

在以下情况下可以使用中介者模式:

  • 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
  • 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。(类中和多个类交集,不好复用,可以使用中介者将这个类和其他类解耦)
  • 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象。
  • 交互的公共行为,如果需要改变行为则可以增加新的中介者类

应用:

MVC架构中控制器

Controller 作为一种中介者,它负责控制视图对象View和模型对象Model之间的交互。如在Struts中,Action就可以作为JSP页面与业务对象之间的中介者。

扩展:

中介者模式与迪米特法则

  • 在中介者模式中,通过创造出一个中介者对象,将系统中有关的对象所引用的其他对象数目减少到最少,使得一个对象与其同事之间的相互作用被这个对象与中介者对象之间的相互作用所取代。因此,中介者模式就是迪米特法则的一个典型应用。

中介者模式与GUI开发

  • 中介者模式可以方便地应用于图形界面(GUI)开发中,在比较复杂的界面中可能存在多个界面组件之间的交互关系。
  • 对于这些复杂的交互关系,有时候我们可以引入一个中介者类,将这些交互的组件作为具体的同事类,将它们之间的引用和控制关系交由中介者负责,在一定程度上简化系统的交互,这也是中介者模式的常见应用之一。

总结:

  • 中介者模式用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。
  • 中介者模式包含四个角色:抽象中介者用于定义一个接口,该接口用于与各同事对象之间的通信;具体中介者是抽象中介者的子类,通过协调各个同事对象来实现协作行为,了解并维护它的各个同事对象的引用;抽象同事类定义各同事的公有方法;具体同事类是抽象同事类的子类,每一个同事对象都引用一个中介者对象;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中定义的方法。
  • 通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的星形结构,中介者承担了中转作用和协调作用。中介者类是中介者模式的核心,它对整个系统进行控制和协调,简化了对象之间的交互,还可以对对象间的交互进行进一步的控制。
  • 中介者模式的主要优点在于简化了对象之间的交互,将各同事解耦,还可以减少子类生成,对于复杂的对象之间的交互,通过引入中介者,可以简化各同事类的设计和实现;中介者模式主要缺点在于具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
  • 中介者模式适用情况包括:系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解;一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象;想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

比较:

中介和命令都是使用加多一个类(具体中介者类,具体命令类)来管理接收者。(具体中介者类,具体命令类里面走包含指向接收者的对象指针)

三:观察者模式

目的:建立对象之间的一种依赖关系,当一个对象发生改变时,将通知其他对象作出反应。改变的对象称观察目标,被通知的对象称观察者。(解决具有依赖关系的类之间的独立和复用)

定义:

观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

观察者模式是一种对象行为型模式。

  • Subject: 目标
  • ConcreteSubject: 具体目标
  • Observer: 观察者
  • ConcreteObserver: 具体观察者
//观察者模式:就是具体目标需要记录知道的观察者对象指针,当自己发生变化时,遍历记录的观察者句柄。
//调用观察者刷新接口,并传递自己的状态/句柄过去
//细粒度抽象:就是具体目标组合了观察者类对象,通过调用自己的接口,间接调用观察者类对象接口
class Subject;
class Observer {
public:
    Observer() {};
    virtual void Update() {};
    virtual void Update(std::string tarket) {};
    virtual void Update(Subject* psub) {};  //直接用指针代替记录的目标句柄,直接用update告知监听的目标变化
    virtual void SetSub(Subject* psub) {  //观察者设置想要观察的目标。也可以不需要设置
        m_sub = psub;
    };
protected:
    int oState;
    std::string name;
    Subject* m_sub;   //观察者需要知道观察的某个目标,这里可以是map存放目标,表明观察者可以观察多个目标,每个目标有自己的标识做判断处理
};

class CreteObserverA :public Observer {
public:
    CreteObserverA(std::string str) {
        name = str;
    };
    virtual void Update(std::string tarket) {  //通过tarket匹配当前是哪个目标出现变化。这样的话,但是
                                              //如果目标身份不是string,类型,观察者这个接口就要改变,感觉不太好
        oState = m_sub->GetState();
        printf("A state = %d\n",oState);
    };
    virtual void Update() {  
        oState = m_sub->GetState();
        printf("A state = %d\n", oState);
    };
    virtual void Update(Subject*psub) {
        oState = psub->GetState();
        printf("A state = %d\n", oState);
    };
};

class CreteObserverB :public Observer {
public:
    CreteObserverB(std::string str) {
        name = str;
    };
    virtual void Update(int state,std::string tarket) {
        oState = m_sub->GetState();//也可以通过state传递具体目标的状态
        printf("B state = %d\n", oState);
    };
    virtual void Update(Subject* psub) {
        oState = psub->GetState();
        printf("B state = %d\n", oState);
    };
};

class Subject {
public:
    Subject() {
    
    };
    virtual std::string GetOwnStr()
    {
        return ownstr;
    }
    virtual void attach(Observer* pobs) {  //目标需要知道自己有多少个观察者
        m_vtObj.push_back(pobs);
        pobs->SetSub(this);  //这里设置观察者需要观察的目标,两者时相互的,目标知道了自己的观察者,证明观察者也要知道观察哪些目标。
    };
    virtual void detach(Observer* pobs) {
        for (vector<Observer*>::iterator itr = m_vtObj.begin();
            itr != m_vtObj.end(); itr++)
        {
            if (*itr == pobs)
            {
                m_vtObj.erase(itr);
                return;
            }
        }
    };
    virtual void Notify() {
        for (vector<Observer*>::iterator itr = m_vtObj.begin();itr != m_vtObj.end();itr++)
        {
            (*itr)->Update(this);//直接传递state也可以的
        }
    };
    virtual void SetState(int state) = 0;
    virtual int GetState() = 0;
protected:
    vector<Observer*> m_vtObj;
    std::string ownstr;//目标的身份
    int state;
};

class CreteSubject :public Subject {
public:
    CreteSubject() {
        ownstr = "Crete1";
    };
    void SetState(int state)
    {  
        state = state;
        //Subject::Notify(); //不能直接这么用,父类并不清楚具体目标存在的观察者。
        //Notify(); //这个可行,但是客户端调用比较好??
    }
    int GetState()
    {
        return state;
    }
};

int main(int argc, char* argv[])
{
    Subject* subject = new CreteSubject();
    Observer* objA = new CreteObserverA("A");
    Observer* objB = new CreteObserverB("B");
    subject->attach(objA);
    subject->attach(objB);

    subject->SetState(1);
    subject->Notify();

    cout << "--------------------" << endl;
    subject->detach(objB);
    subject->SetState(2);
    subject->Notify();

    delete subject;
    delete objA;
    delete objB;

    return 0;
}

分析:

  • 观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。
  • 这一模式中的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知。
  • 作为对这个通知的响应,每个观察者都将即时更新自己的状态,以与目标状态同步,这种交互也称为发布-订阅(publishsubscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知(发布者直接将通知发布出去,谁需要处理这个数据就,哪个观察者收到通知后就去处理,不需要处理的,可以直接过滤)

优点:

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
  • 观察者模式支持广播通信。
  • 观察者模式符合“开闭原则”的要求。

缺点:

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化

适用:

在以下情况下可以使用观察者模式:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。(可以建立促发连,可以让每一个对象的改变通知到自己的观观察者)
  • 凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式

扩展:MVC模式(使用了中介和观察)

  • MVC模式是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。观察者模式可以用来实现MVC模式,观察者模式中的观察目标就是MVC模式中的模型(Model),而观察者就是MVC中的视图(View),控制器(Controller)充当两者之间的中介者(Mediator)。当模型层的数据发生改变时,视图层将自动改变其显示内容。

总结:

  • 观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅模式、模型-视图模式、源-监听器模式或从属者模式。观察者模式是一种对象行为型模式。
  • 观察者模式包含四个角色:目标又称为主题,它是指被观察的对象;具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者将对观察目标的改变做出反应;在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
  • 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
  • 观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式适用情况包括:一个抽象模型有两个方面,其中一个方面依赖于另一个方面;一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变;一个对象必须通知其他对象,而并不知道这些对象是谁;需要在系统中创建一个触发链。
  • 在JDK的java.util包中,提供了Observable类以及Observer接口,它们构成了Java语言对观察者模式的支持

四:状态模式

C++之状态(State)模式_c++ 状态模式-CSDN博客

解决状态更改,行为也跟随改变

定义:

状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式

  • Context: 环境类
  • State: 抽象状态类
  • ConcreteState: 具体状态类

五:策略模式

解决算法太多的选择处理问题:

可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。

定义:

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。策略模式是一种对象行为型模式。

  • Context: 环境类
  • Strategy: 抽象策略类
  • ConcreteStrategy: 具体策略类
class Strategy {
public:
    Strategy() {};
    virtual doStrategy() = 0;

};

class StrategyA :public Strategy {
public:
    StrategyA() {};
    virtual doStrategy() {
        printf("do Strategy A\n");
    };
};

class StrategyB :public Strategy {
public:
    StrategyB() {};
    virtual doStrategy() {
        printf("do Strategy B\n");
    };
};

class context{
public:
    context() {
    };
    virtual void doStrategy() {
        m_Strategy.doStrategy();
    };

    virtual void SetStrategy(Strategy* pStrategy) {  //设置方式,可以支持多种算法传递调用。
                                                     //需要传递哪个句柄和判断交给用户处理
        m_Strategy = pStrategy;
    };

private:
    Strategy* m_Strategy;
};

int main(int argc, char* argv[])
{
    Strategy* s1 = new StrategyA();
    Context* cxt = new Context();
    cxt->setStrategy(s1);   //算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性
    cxt->doStrategy();

    Strategy* s2 = new StrategyB();
    cxt->setStrategy(s2);
    cxt->doStrategy();

    delete s1;
    delete s2;
}

优点:

  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 策略模式提供了管理相关的算法族的办法。
  • 策略模式提供了可以替换继承关系的办法。
  • 使用策略模式可以避免使用多重条件转移语句

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量

扩展:策略和状态模式

  • 策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类(时换环境类有策略类,策略类无环境类);而状态模式的环境类由于外在因素需要放进一个具体状态中,以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系(状态模式中环境类和状态类彼此有彼此对象指针,就是实现通过环境类切换状态行为)
  • 使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换
  • 如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。(策略模式处理一个行为有多个实现方式,并可相互替换。状态模式处理一个对象有多个状态,并且各状态下的行为有差异,彼此可以相互转换)

总结:

  • 在策略模式中定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式。策略模式是一种对象行为型模式。
  • 策略模式包含三个角色:环境类在解决某个问题时可以采用多种策略,在环境类中维护一个对抽象策略类的引用实例;抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类;具体策略类实现了在抽象策略类中定义的算法。
  • 策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。
  • 策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法,它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区别,同时在一定程度上增加了系统中类的个数,可能会存在很多策略类。
  • 策略模式适用情况包括:在一个系统里面有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为;一个系统需要动态地在几种算法中选择一种;避免使用难以维护的多重条件选择语句;希望在具体策略类中封装算法和与相关的数据结构。
文章来源:https://blog.csdn.net/lilijianqun/article/details/135715759
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。