ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

设计模式学习

2026/9/19 3:53:10 拓冰建站 浏览量
设计模式学习

面向对象有两个思维模型:

  1. 底层思维:向下,如何把握机器底层从微观理解对象构造,封装、继承、多态
  2. 抽象思维:向上,如何将我们周围世界抽象为程序代码,包括面向对象、组件封装、设计模式、架构模式

软件设计的目标:复用!复用!复用!

重新认识面向对象:
传统的面向对象是:封装、继承、多态
从软件设计层面来看,面向对象是:

  1. 隔离变化:构建方式更能适应软件的变化,能将变化带来的影响减为最小
  2. 各司其责:由于需求变化导致的新增类型不应该影响原来类型的实现

面向对象设计原则:

  • 依赖倒置原则:高层模块(稳定)应该依赖于抽象(稳定),实现细节也应该依赖于抽象(稳定)
  • 开放封闭原则:对拓展开放,对更改封闭.类模块应该是可扩展的,但是不可修改
  • 单一职责原则:一个类应该仅有一个引起它变化的原因。变化的方向隐含着类的责任
  • Liskov替换原则:子类必须能够替换他们的基类(IS-A)。继承表达类型抽象
  • 接口隔离原则:不应该强迫用户程序依赖他们不用的方法。接口应该小而完备
  • 优先使用对象组合,而不是类继承
  • 封装变化点:使用封装来创建对象之间的分界层,让设计者可以在分界层一侧进行修改,而不会对另一侧产生不良影响,从而实现层次间的松耦合
  • 针对接口编程,而不是针对实现编程
     

23种设计模式的分类:

  1. 创建型:解决对象的创建工作
  2. 结构型:解决在需求变化时对对象的结构带来的冲击
  3. 行为型:应对需求变化为多个交互的类带来的冲击

老师的划分方法:

  • 组件协作:Template Method、Strategy、Observer
  • 单一职责:Decorator、Bridge
  • 对象创建:Factory Method、Abstract Factory、Prototype、Builder
  • 对象性能:Singleton、Flyweight
  • 接口隔离:Facade、Proxy、Mediator、Adapter
  • 状态变化:Memento、State
  • 数据结构:Composite、Iterator、Chain of Resposibility
  • 行为变化:Command、Vistor
  • 领域问题:Interpreter
     

重构关键技法:

  1. 静态—动态
  2. 早绑定—晚绑定
  3. 继承—组合
  4. 编译时依赖—运行时依赖
  5. 紧耦合—松耦合

组件协作

通过晚绑定来实现框架与应用程序之间的松耦合

模板方法:Template Method

  1. 动机

在软件构建过程中,对于某一项任务,她常常有稳定的整体操作结构,但各个子步骤却又很多改变的需求,或者由于固有的原因而无法和任务的整体结构同时实现。

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

2.伪代码:

//程序库开发人员
class Library{
public://稳定 template methodvoid 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;}
}

3.理解:

从代码可以看出,其实就是在父类中定义了虚函数,子类中将其实现,这里的父类就可以看成是一个模板,后续使用中,照着这个模板进行改进就行。这利用了C++语言的虚函数特点实现了晚绑定的思想。“让我来调用你,你别调用我”。

父类的析构函数一般写成虚函数,这一点要注意

策略模式:Strategy

  1. 动机

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

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

2.伪代码:

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); //多态调用//...}};

3.理解

如果不使用Strategy模式,那么就要用if-else来实现上面代码的拓展,那么这就要去源码中修改判断条件,这个打破了开闭原则!所以,又把计算的功能作为一个纯虚函数,然后要拓展的时候,继承父类,重写计算方法,在调用的时候使用虚函数的调用特性,实现该模式。定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
所谓的策略,就是新增的一种方法,和原有的方法是同一级的,我要把它加进来,但是不能够影响原来的代码,就需要策略模式来实现。
有if-else的代码,就有使用策略模式的苗头!除非不是情况完全不可能改变,比如性别,星期几等,否则尽量少用if-else。从内存角度看,使用if-else会让内存加载一些可能不会用到的数据,从而降低性能。

4.类图

观察者模式:Observer

  1. 动机

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

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

2.伪代码:

class IProgress{
public:virtual void DoProgress(float value)=0;virtual ~IProgress(){}
};class FileSplitter
{string m_filePath;int m_fileNumber;List<IProgress*>  m_iprogressList; // 抽象通知机制,支持多个观察者public:FileSplitter(const string& filePath, int fileNumber) :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.push_back(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());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 << ".";}
};

3.理解:

使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。比如说鼠标监听事件等~

4.类图

单一职责

装饰模式:Decorator

组合优于继承

  1. 动机

在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。

如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?

 2.伪代码:

//业务操作
class Stream{public:virtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}
};//主体类
class FileStream: public Stream{
public:virtual char Read(int number){//读文件流}virtual void Seek(int position){//定位文件流}virtual void Write(char data){//写文件流}};class NetworkStream :public Stream{
public:virtual char Read(int number){//读网络流}virtual void Seek(int position){//定位网络流}virtual void Write(char data){//写网络流}};class MemoryStream :public Stream{
public:virtual char Read(int number){//读内存流}virtual void Seek(int position){//定位内存流}virtual void Write(char data){//写内存流}};//扩展操作DecoratorStream: public Stream{
protected:Stream* stream;//...DecoratorStream(Stream * stm):stream(stm){}};class CryptoStream: public DecoratorStream {public:CryptoStream(Stream* stm):DecoratorStream(stm){}virtual char Read(int number){//额外的加密操作...stream->Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}
};class BufferedStream : public DecoratorStream{Stream* stream;//...public:BufferedStream(Stream* stm):DecoratorStream(stm){}//...
};void Process(){//运行时装配FileStream* s1=new FileStream();CryptoStream* s2=new CryptoStream(s1);BufferedStream* s3=new BufferedStream(s1);BufferedStream* s4=new BufferedStream(s2);
}

3.理解:

这个设计模式体现了“组合优于继承”的思想!Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。继承是为了完善接口的规范,组合是为了后期的装饰实现

4.类图:

桥模式:Bridge

  1. 动机

由于某些类型的固有的实现逻辑,使得它们具有两个维度的变化,乃至多个纬度的变化。如何应对这种“多维度的变化”?

如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?

2.伪代码:

class Messager{
protected:MessagerImp* messagerImp;//...
public:virtual void Login(string username, string password)=0;virtual void SendMessage(string message)=0;virtual void SendPicture(Image image)=0;virtual ~Messager(){}
};class MessagerImp{
public:virtual void PlaySound()=0;virtual void DrawShape()=0;virtual void WriteText()=0;virtual void Connect()=0;virtual MessagerImp(){}
};//平台实现 n
class PCMessagerImp : public MessagerImp{
public:virtual void PlaySound(){//**********}virtual void DrawShape(){//**********}virtual void WriteText(){//**********}virtual void Connect(){//**********}
};class MobileMessagerImp : public MessagerImp{
public:virtual void PlaySound(){//==========}virtual void DrawShape(){//==========}virtual void WriteText(){//==========}virtual void Connect(){//==========}
};//业务抽象 m//类的数目:1+n+mclass MessagerLite :public Messager {public:virtual void Login(string username, string password){messagerImp->Connect();//........}virtual void SendMessage(string message){messagerImp->WriteText();//........}virtual void SendPicture(Image image){messagerImp->DrawShape();//........}
};class MessagerPerfect  :public Messager {public:virtual void Login(string username, string password){messagerImp->PlaySound();//********messagerImp->Connect();//........}virtual void SendMessage(string message){messagerImp->PlaySound();//********messagerImp->WriteText();//........}virtual void SendPicture(Image image){messagerImp->PlaySound();//********messagerImp->DrawShape();//........}
};void Process(){//运行时装配MessagerImp* mImp=new PCMessagerImp();Messager *m =new Messager(mImp);
}

3.理解:

桥体现在哪里呢?代码中的MessagerImp就是这个桥,Messager中组合了这个桥,它的子类可以通过这个桥去实现Messager中的抽象方法。Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式,用桥把这两个方向给连接起来。

4.类图:

对象创建

通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。

工厂方法:Factory Method

  1. 动机

在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

2.伪代码:


//抽象类
class ISplitter{
public:virtual void split()=0;virtual ~ISplitter(){}
};//工厂基类
class SplitterFactory{
public:virtual ISplitter* CreateSplitter()=0;virtual ~SplitterFactory(){}
};//------------------------------------------------------------------//具体类
class BinarySplitter : public ISplitter{};class TxtSplitter: public ISplitter{};class PictureSplitter: public ISplitter{};class VideoSplitter: public ISplitter{};//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new BinarySplitter();}
};class TxtSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new TxtSplitter();}
};class PictureSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new PictureSplitter();}
};class VideoSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new VideoSplitter();}
};//------------------------------------------------------------------class MainForm : public Form
{SplitterFactory*  factory;//工厂public:MainForm(SplitterFactory*  factory){this->factory=factory;}void Button1_Click(){ISplitter * splitter=factory->CreateSplitter(); //多态newsplitter->split();}
};

3.理解:

称之为多态new。Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。

4.类图:

抽象工厂:Abstract Factory

  1. 动机

在软件系统中,经常面临着"一系列相互依赖的对象"的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?

2.伪代码:


//数据库访问有关的基类
class IDBConnection{};class IDBCommand{};class IDataReader{};class IDBFactory{
public:virtual IDBConnection* CreateDBConnection()=0;virtual IDBCommand* CreateDBCommand()=0;virtual IDataReader* CreateDataReader()=0;};//支持SQL Server
class SqlConnection: public IDBConnection{};
class SqlCommand: public IDBCommand{};
class SqlDataReader: public IDataReader{};class SqlDBFactory:public IDBFactory{
public:virtual IDBConnection* CreateDBConnection()=0;virtual IDBCommand* CreateDBCommand()=0;virtual IDataReader* CreateDataReader()=0;};//支持Oracle
class OracleConnection: public IDBConnection{};class OracleCommand: public IDBCommand{};class OracleDataReader: public IDataReader{};class EmployeeDAO{IDBFactory* dbFactory;public:vector<EmployeeDO> GetEmployees(){IDBConnection* connection =dbFactory->CreateDBConnection();connection->ConnectionString("...");IDBCommand* command =dbFactory->CreateDBCommand();command->CommandText("...");command->SetConnection(connection); //关联性IDBDataReader* reader = command->ExecuteReader(); //关联性while (reader->Read()){}}
};

3.理解:

工厂的工厂。系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。

4.类图:

原型模式:Prototype

相比于工厂的两种方法,用得较少。抽象工厂是适合“一系列相互依赖的对象”,原型方法适合“一个结构复杂的对象”

  1. 动机

在软件系统中,经常面临一些“某些结构复杂的对象”的创建工作;由于需求变化,这些对象面临着剧烈的变化,但是他们却拥有比较稳定一致的借口。如何应对这种变化?如何向客户程序隔离出这些易变对象,从而使得“依赖这些易变对象的客户程序”不随着需求的改变而改变?

2.伪代码:

//抽象类
class ISplitter{
public:virtual void split()=0;virtual ISplitter* clone()=0; //通过克隆自己来创建对象virtual ~ISplitter(){}};//具体类
class BinarySplitter : public ISplitter{
public:virtual ISplitter* clone(){return new BinarySplitter(*this);}
};class TxtSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new TxtSplitter(*this);}
};class PictureSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new PictureSplitter(*this);}
};class VideoSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new VideoSplitter(*this);}
};class MainForm : public Form
{ISplitter*  prototype;//原型对象public:MainForm(ISplitter*  prototype){this->prototype=prototype;}void Button1_Click(){ISplitter * splitter=prototype->clone(); //克隆原型splitter->split();}
};

3.理解:

通过克隆自己来创建对象。所谓原型,就是在具体类中有一个原型,但是他不是供使用的,而是用给别人克隆(深拷贝)的!

4.类图:

构建器:Builder

  1. 动机

在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?

2.伪代码:


class House{//....
};class HouseBuilder {
public:House* GetResult(){return pHouse;}virtual ~HouseBuilder(){}
protected:House* pHouse;virtual void BuildPart1()=0;virtual void BuildPart2()=0;virtual void BuildPart3()=0;virtual void BuildPart4()=0;virtual void BuildPart5()=0;};class StoneHouse: public House{};class StoneHouseBuilder: public HouseBuilder{
protected:virtual void BuildPart1(){//pHouse->Part1 = ...;}virtual void BuildPart2(){}virtual void BuildPart3(){}virtual void BuildPart4(){}virtual void BuildPart5(){}};class HouseDirector{public:HouseBuilder* pHouseBuilder;HouseDirector(HouseBuilder* pHouseBuilder){this->pHouseBuilder=pHouseBuilder;}House* Construct(){pHouseBuilder->BuildPart1();for (int i = 0; i < 4; i++){pHouseBuilder->BuildPart2();}bool flag=pHouseBuilder->BuildPart3();if(flag){pHouseBuilder->BuildPart4();}pHouseBuilder->BuildPart5();return pHouseBuilder->GetResult();}
};

3.理解:

有点像模板方法。builder就是类中的init()方法。把house和houseBuild分离。

4.类图:

对象性能

单件模式:Singleton

  1. 动机

有些特殊的类,必须保证他们在系统中之存在一个实例。

2.伪代码:


class Singleton{
private:Singleton();Singleton(const Singleton& other);
public:static Singleton* getInstance();static Singleton* m_instance;
};Singleton* Singleton::m_instance=nullptr;//线程非安全版本
Singleton* Singleton::getInstance() {if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;
}//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {Lock lock;if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;
}//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {if(m_instance==nullptr){Lock lock;if (m_instance == nullptr) {m_instance = new Singleton();}}return m_instance;
}//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;Singleton* Singleton::getInstance() {Singleton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);//获取内存fenceif (tmp == nullptr) {std::lock_guard<std::mutex> lock(m_mutex);tmp = m_instance.load(std::memory_order_relaxed);if (tmp == nullptr) {tmp = new Singleton;std::atomic_thread_fence(std::memory_order_release);//释放内存fencem_instance.store(tmp, std::memory_order_relaxed);}}return tmp;
}

3.理解:

私有构造函数,自身私有的静态对象变量,提供一个getInstance方法。要考虑线程安全的问题。

4.类图:

享元模式:Flyweight

  1. 动机

采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥内存,从而带来很高的运行时代价。如何在避免大量细粒度对象问题的同时,让外部客户仍然能够透明的使用面向对象的方式进行操作。

2.伪代码:


class Font {
private://unique object keystring key;//object state//....public:Font(const string& key){//...}
};class FontFactory{
private:map<string,Font* > fontPool;public:Font* GetFont(const string& key){map<string,Font*>::iterator item=fontPool.find(key);if(item!=footPool.end()){return fontPool[key];}else{Font* font = new Font(key);fontPool[key]= font;return font;}}void clear(){//...}
};

3.理解:

比如说线程池的设计思想就是享元模式。享元主要解决面向对象的代价问题,不触及面向对象的抽象问题。

4.类图:

接口隔离

在组件构建过程中,某些接口之间的直接依赖常常会带来很多问题、甚至根本无法实现。采用添加一层间接接口,来隔离本来互相紧密关联的接口是一种常见的解决方案。操作系统、软件、用户关系中,软件就是中间层。

门面模式:Facade

  1. 动机

上图中A方案用户和组件有过多的耦合,这种过多的耦合会带来很多问题。如何简化外部客户程序和系统间的接口?如何将外部客户程序的演化和内部子系统之间的变化之间的依赖相互解耦?

2.理解:

不管facade内部怎么变化,对于用户来说,接口都是一样的。对于facade的内外部来说,是将内部和外部进行解耦了。facade内部应该是“相互耦合关系比较大的一系列组件”。也就是松耦合,高内聚的思想。

3.类图:

参考文章:侯捷C++八部曲笔记(三、设计模式)_侯捷c++设计模式-CSDN博客