整理自李建忠老师的《C++设计模式》视频教程
视频:C++设计模式[李建忠]:
“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。——Christopher Alexander
历史性著作《设计模式:可复用面向对象软件的基础》一书中描述了23种经典面向对象设计模式,创立了模式在软件设计中的地位。
由于《设计模式》一书确定了设计模式的地位,通常所说的设计模式隐含地表示“面向对象设计模式”。但这并不意味“设计模式"就等于“面向对象设计模式”。
分解
抽象
现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。——《设计模式》GoF
//--------------------------class MainForm---------------------------------//
class MainForm : public Form
{
TextBox *txtFilePath;
TextBox *txtFileNumber;
ProgressBar *progressBar; //观察者 //如:进度条显示
public:
void Button1_Click()
{
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, progressBar); //传递给下一级
splitter.split();
}
};
//--------------------------class FileSplitter---------------------------------//
class FileSplitter
{
string m_filePath;
int m_fileNumber;
ProgressBar *m_progressBar; //依赖于上一级,所以要解决这种依赖关系
public:
FileSplitter(const string &filePath,
int fileNumber,
ProgressBar *progressBar)
: m_filePath(filePath),
m_fileNumber(fileNumber),
m_progressBar(progressBar)
{
}
void split()
{
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++)
{
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue);
}
}
};
//-----------------------------------------------------------//
//-----------------------class IProgress------class MainForm------------------------------//
//观察者 - 基类
class IProgress
{
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress() {
}
};
// MainForm 是一个观察者 - 子类
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;
//此class的对象负责:分隔文件为几份
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 << ".";
}
};
//--------------------------class FileSplitter---------------------------------//
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++;
}
};
//-----------------------------------------------------------//
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
观察者自己决定是否需要订阅通知,目标对象对此一无所知。
Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。——《设计模式》GoF
//--------------------------class MainForm---------------------------------//
class MainForm : public Form
{
TextBox *txtFilePath;
TextBox *txtFileNumber;
public:
void Button1_Click()
{
ISplitter *splitter =
new BinarySplitter(); //分隔文件//依然依赖具体类->需要改进
splitter->split();
}
};
//----------------------------class ISplitter-------------------------------//
class ISplitter
{
public:
virtual void split() = 0;
virtual ~ISplitter() {
}
};
class BinarySplitter : public ISplitter // 二进制格式 分隔文件
{
};
class TxtSplitter : public ISplitter // 分隔文本
{
};
class PictureSplitter : public ISplitter // 分隔图片
{
};
class VideoSplitter : public ISplitter // 分隔视频
{
};
//-----------------------------------------------------------//
//--------------------------class MainForm---------------------------------//
class MainForm : public Form
{
SplitterFactory *factory; //工厂类 - 父类指针
public:
MainForm(SplitterFactory *factory)
{
this->factory = factory;//外界(用户) 自己指定 具体 子工厂类对象
}
void Button1_Click()
{
ISplitter *splitter =
factory->CreateSplitter(); //多态
splitter->split();
}
};
//-------------------class ISplitter-----class SplitterFactory--------------//
//分隔类-基类
class ISplitter
{
public:
virtual void split() = 0;
virtual ~ISplitter() {
}
};
//工厂类-基类
class SplitterFactory
{
public:
virtual ISplitter *CreateSplitter() = 0;
virtual ~SplitterFactory() {
}
};
//-------------class ISplitter的子类-----class 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();
}
};
//-----------------------------------------------------------//
Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
在软件系统中,经常面临着 “一系列相互依赖的对象” 的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?
提供一个接口,让该接口负责创建一系列 “相关或者相互依赖的对象” ,无需指定它们具体的类。——《设计模式》GoF
class EmployeeDAO
{
public:
vector<EmployeeDO> GetEmployees()
{
//连接数据库
SqlConnection *connection =
new SqlConnection();
connection->ConnectionString = "...";
//执行SQL语句
SqlCommand *command =
new SqlCommand();
command->CommandText = "...";
command->SetConnection(connection); //关联性
//读取执行结果
SqlDataReader *reader = command->ExecuteReader(); //关联性
while (reader->Read())
{
}
}
};
//--------------------------------------------------//
class IDBConnection{
};
class IDBConnectionFactory
{
public:
virtual IDBConnection *CreateDBConnection() = 0;
};
class SqlConnection : public IDBConnection{
}; //支持SQL Server
class SqlConnectionFactory : public IDBConnectionFactory{
}; //支持SQL Server的factory
class OracleConnection : public IDBConnection{
}; //支持Oracle
//--------------------------------------------------//
class IDBCommand{
};
class IDBCommandFactory
{
public:
virtual IDBCommand *CreateDBCommand() = 0;
};
class SqlCommand : public IDBCommand{
}; //支持SQL Server
class SqlCommandFactory : public IDBCommandFactory{
};
class OracleCommand : public IDBCommand{
}; //支持Oracle
//--------------------------------------------------//
class IDataReader{
};
class IDataReaderFactory
{
public:
virtual IDataReader *CreateDataReader() = 0;
};
class SqlDataReader : public IDataReader{
}; //支持SQL Server
class SqlDataReaderFactory : public IDataReaderFactory{
};
class OracleDataReader : public IDataReader{
}; //支持Oracle
//--------------------------------------------------//
class EmployeeDAO
{
IDBConnectionFactory *dbConnectionFactory;
IDBCommandFactory *dbCommandFactory;
IDataReaderFactory *dataReaderFactory;
public:
vector<EmployeeDO> GetEmployees()
{
IDBConnection *connection =
dbConnectionFactory->CreateDBConnection();
connection->ConnectionString("...");
IDBCommand *command =
dbCommandFactory->CreateDBCommand();
command->CommandText("...");
command->SetConnection(connection); //关联性
IDBDataReader *reader = command->ExecuteReader(); //关联性
while (reader->Read())
{
}
}
};
如果用户不小心把 mysql的connection 和 oracle的command 混搭在一起,那将会出错!
所以要把这些有关系的 一系列 操作,封装到一起,这就是 Abstract Factory的思想,代码如下:
//-----------------------------------------------------------//
//数据库访问有关的基类
class IDB
{
DBConnection();
DBCommand();
DataReader();
};
class IDBFactory
{
public:
virtual IDBConnection *CreateDBConnection() = 0;
virtual IDBCommand *CreateDBCommand() = 0;
virtual IDataReader *CreateDataReader() = 0;
};
//支持SQL Server
class SqlDBFactory : public IDBFactory
{
public:
virtual IDBConnection *CreateDBConnection() = 0;
virtual IDBCommand *CreateDBCommand() = 0;
virtual IDataReader *CreateDataReader() = 0;
};
//支持Oracle
class OracleDBFactory : public IDBFactory
{
public:
virtual IDBConnection *CreateDBConnection() = 0;
virtual IDBCommand *CreateDBCommand() = 0;
virtual IDataReader *CreateDataReader() = 0;
};
class EmployeeDAO
{
IDBFactory *dbFactory;
public:
vector<EmployeeDO> GetEmployees()
{
//连接数据库
IDBConnection *connection =
dbFactory->CreateDBConnection();
connection->ConnectionString("...");
//执行SQL语句
IDBCommand *command =
dbFactory->CreateDBCommand();
command->CommandText("...");
command->SetConnection(connection); //关联性
//读取执行结果
IDBDataReader *reader = command->ExecuteReader(); //关联性
while (reader->Read())
{
}
}
};
//-----------------------------------------------------------//
如果没有应对 “多系列对象构建” 的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂完全可以。
“系列对象” 指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。
Abstract Factory模式主要在于应对“新系列”的需求变动(如:增加一个系列)。其缺点在于难以应对“新对象”的需求变动(对一个系列的基类进行更改)。
面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
这应该是类设计者的责任,而不是使用者的责任。
保证一个类仅有一个实例,并提供一个该实例的全局访问点。——《设计模式》GoF
#include
#include
#include
using namespace std;
typedef mutex Lock;
class Singleton
{
private:
Singleton();
Singleton(const Singleton &other);
public:
static Singleton *getInstance1();
static Singleton *getInstance2();
static Singleton *getInstance3();
static Singleton *getInstance4();
static Singleton *m_instance;
};
Singleton *Singleton::m_instance = nullptr;
//线程非安全版本(单线程时可以使用,多线程时不行)
Singleton *Singleton::getInstance1()
{
if (m_instance == nullptr)
{
m_instance = new Singleton();
}
return m_instance;
}
//线程安全版本,但锁的代价过高(多线程时,很多读取操作的话,会影响性能)
Singleton *Singleton::getInstance2()
{
Lock lock;
if (m_instance == nullptr)
{
m_instance = new Singleton();
}
return m_instance;
}
//双检查锁,但由于内存读写reorder不安全
Singleton *Singleton::getInstance3()
{
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;
std::mutex m_mutex;
Singleton *Singleton::getInstance4()
{
Singleton *tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); //获取内存fence
if (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); //释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
Singleton模式中的实例构造器可以设置为protected以允许子类派生。
Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。
如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。
常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显式指定,将必不可少地带来请求发送者与接受者的紧耦合。
如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。——《设计模式》GoF
#include
#include
using namespace std;
enum class RequestType
{
REQ_HANDLER1,
REQ_HANDLER2,
REQ_HANDLER3
};
class Reqest
{
string description;
RequestType reqType;
public:
Reqest(const string &desc, RequestType type) : description(desc), reqType(type) {
}
RequestType getReqType() const {
return reqType; }
const string &getDescription() const {
return description; }
};
class ChainHandler
{
public:
ChainHandler() {
nextChain = nullptr; }
void setNextChain(ChainHandler *next) {
nextChain = next; }
void handle(const Reqest &req)
{
if (canHandleRequest(req))
processRequest(req);
else
sendReqestToNextHandler(req);
}
private:
ChainHandler *nextChain;
void sendReqestToNextHandler(const Reqest &req)
{
if (nextChain != nullptr)
nextChain->handle(req);
}
protected:
virtual bool canHandleRequest(const Reqest &req) = 0;
virtual void processRequest(const Reqest &req) = 0;
};
class Handler1 : public ChainHandler
{
protected:
bool canHandleRequest(const Reqest &req) override
{
return req.getReqType() == RequestType::REQ_HANDLER1;
}
void processRequest(const Reqest &req) override
{
cout << "Handler1 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler2 : public ChainHandler
{
protected:
bool canHandleRequest(const Reqest &req) override
{
return req.getReqType() == RequestType::REQ_HANDLER2;
}
void processRequest(const Reqest &req) override
{
cout << "Handler2 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler3 : public ChainHandler
{
protected:
bool canHandleRequest(const Reqest &req) override
{
return req.getReqType() == RequestType::REQ_HANDLER3;
}
void processRequest(const Reqest &req) override
{
cout << "Handler3 is handle reqest: " << req.getDescription() << endl;
}
};
int main()
{
Handler1 h1;
Handler2 h2;
Handler3 h3;
h1.setNextChain(&h2);
h2.setNextChain(&h3);
Reqest req("process task ... ", RequestType::REQ_HANDLER3);
h1.handle(req);
return 0;
}
Chain of Responsibility 模式的应用场合在于 “一个请求可能有多个接受者,但是最后真正的接受者只有一个” ,这时候请求发送者与接受者的耦合有可能出现“变化脆弱”的症状,职责链的自的就是将二者解耦,从而更好地应对变化。
应用了Chain of Responsibility模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加/修改请求的处理职责。
如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。