C++设计模式(李建忠)笔记2
C++设计模式(李建忠)
本文是学习笔记,如有侵权,请联系删除。
参考链接
Youtube: C++设计模式
Gtihub源码与PPT:https://github.com/ZachL1/Bilibili-plus
豆瓣: 设计模式–可复用面向对象软件的基础
文章目录
- C++设计模式(李建忠)
- 8 工厂方法(Factory Method)
- Motivation
- 工厂方法定义
- 工厂方法结构
- 9 抽象工厂(Abstract Factory)
- Motivation
- 抽象工厂定义
- 抽象工厂结构
- 10 原型模式(Prototype)
- 原型方法定义
- 原型方法结构
- 原型模式和工厂方法的区别
- 11 构建器(Builder)
- Motivation
- Builder定义
- Builder结构
- 12 单例模式(Singleton)
- 单例模式定义
- 单例模式结构
- 13 享元模式(Flyweight)
- 享元模式定义
- 享元模式结构
- 后记
“对象创建”模式
通过“对象创建”模式绕开new,来避免对象创建(new)过程 中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
典型模式
·Factory Method
·Abstract Factory
·Prototype
·Builder
8 工厂方法(Factory Method)
Motivation
在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种封装机制来避免客户程序和这种具体对象创建工作的紧耦合?
代码示例:
MainForm方法调用Splitter将东西进行切分,有二进制文件切分、txt文件切分、图片文件切分、视频文件切分。
第一版:ISplitter * splitter= new BinarySplitter();
这种写法依赖于具体类,违反依赖倒置原则(高层模块不应该依赖于低层模块,而是应该依赖于抽象。同时,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。)
// 抽象基类
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 : public Form
{TextBox* txtFilePath;TextBox* txtFileNumber;ProgressBar* progressBar;public:void Button1_Click(){ISplitter * splitter= new BinarySplitter();//依赖具体类splitter->split();}
};
第二版:具体调用工厂,MainForm不依赖于具体的类,而是依赖于抽象类和工厂基类。
class MainForm : public Form
{SplitterFactory* factory;//工厂public:MainForm(SplitterFactory* factory){this->factory=factory;}void Button1_Click(){ISplitter * splitter=factory->CreateSplitter(); //多态newsplitter->split();}
};
ISplitterFactory
//抽象类
class ISplitter{
public:virtual void split()=0;virtual ~ISplitter(){}
};//工厂基类
class SplitterFactory{
public:virtual ISplitter* CreateSplitter()=0; // 创建对象virtual ~SplitterFactory(){}
};
FileSplitter文件:几个具体的工厂继承自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使一个类的实例化延迟到其子类。
具体到上面的代码,用于创建对象的接口SplitterFactory,让子类(BinarySplitterFactory,TxtSplitterFactory等具体工厂)决定实例化哪一个类。
在下列情况下可以使用Factory Method模式:
• 当一个类不知道它所必须创建的对象的类的时候。
• 当一个类希望由它的子类来指定它所创建的对象的时候。
• 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。(classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.)
工厂方法结构
参与者
• Product(ISplitter)
— 定义工厂方法所创建的对象的接口。
• ConcreteProduct(BinarySplitter,TxtSplitter,PictureSplitter,VideoSplitter)
— 实现Product接口。
• Creator(SplitterFactory)
— 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。
— 可以调用工厂方法以创建一个Product对象。
• ConcreteCreator(BinarySplitterFactory,TxtSplitterFactory,PictureSplitterFactory,VideoSplitterFactory)
— 重定义工厂方法以返回一个ConcreteProduct实例。
工厂方法总结
Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
Factory Method模式通过面向对象(多态)的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
Factory Method模式解决单个对象的需求变化,缺点在于创建方法或者传入的参数要相同。
9 抽象工厂(Abstract Factory)
Motivation
在软件系统中,经常面临着一系列相互依赖的对象的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种封装机制来避免客户程序和这种多系列具体对象创建工作的紧耦合?
代码示例
创建数据访问层,支持多种数据库
第一版:不好的设计,绑定到具体的类
class EmployeeDAO{public:vector<EmployeeDO> GetEmployees(){SqlConnection* connection =new SqlConnection();connection->ConnectionString = "...";SqlCommand* command =new SqlCommand();command->CommandText="...";command->SetConnection(connection);SqlDataReader* reader = command->ExecuteReader();while (reader->Read()){}}
};
第二版:使用工厂方法,但是会遇到问题,使用的时候工厂之间有关联性,必须是同一系列的,比如都是oracle数据库的连接、命令、datareader等,不能是mysql的连接、orable的命令这种交叉使用。
// 三个对象必须是同系列(同组),有关联性IDBConnectionFactory* dbConnectionFactory;IDBCommandFactory* dbCommandFactory;IDataReaderFactory* dataReaderFactory;
工厂方法的代码如下
//数据库访问有关的基类
class IDBConnection{};
class IDBConnectionFactory{
public:virtual IDBConnection* CreateDBConnection()=0;
};class IDBCommand{};
class IDBCommandFactory{
public:virtual IDBCommand* CreateDBCommand()=0;
};class IDataReader{};
class IDataReaderFactory{
public:virtual IDataReader* CreateDataReader()=0;
};//支持SQL Server
class SqlConnection: public IDBConnection{};
class SqlConnectionFactory:public IDBConnectionFactory{};class SqlCommand: public IDBCommand{};
class SqlCommandFactory:public IDBCommandFactory{};class SqlDataReader: public IDataReader{};
class SqlDataReaderFactory:public IDataReaderFactory{};//支持Oracle
class OracleConnection: public IDBConnection{};class OracleCommand: public IDBCommand{};class OracleDataReader: public IDataReader{};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()){}}
};
第三版:有关联性的创建行为放在一起,合成一个工厂,这就是抽象工厂Abstract Factory
//数据库访问有关的基类
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()){}}
};
抽象工厂定义
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂结构
参与者
• AbstractFactory (IDBFactory) — 声明一个创建抽象产品对象的操作接口。
• ConcreteFactory (SqlDBFactory,OracleDBFactory等) — 实现创建具体产品对象的操作。
• AbstractProduct (IDBConnection,IDBCommand等) — 为一类产品对象声明一个接口。
• ConcreteProduct (SqlConnection, OracleCommand等) — 定义一个将被相应的具体工厂创建的产品对象。实现AbstractProduct接口。
• Client — 仅使用由AbstractFactory和AbstractProduct类声明的接口。
协作
• 通常在运行时刻创建一个ConcreteFactory类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应使用不同的具体工厂。
• AbstractFactory将产品对象的创建延迟到它的ConcreteFactory子类。
要点总结
如果没有应对多系列对象构建的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂即可。
系列对象指的是在某一特定系列下的对象之间有相互依赖或相互作用的关系。不同系列的对象之间不能相互依赖。
10 原型模式(Prototype)
Motivation
在软件系统中,经常面临着某些结构复杂的对象的创建;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。
如何应对这种变化?如何向客户程序隔离出这些易变对象,从而使得依赖这些易变对象的客户程序不随着需求改变而改变?
代码示例
//抽象类
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);}
};
Client怎么用原型方法
class MainForm : public Form
{ISplitter* prototype;//原型对象public:MainForm(ISplitter* prototype){this->prototype=prototype;}void Button1_Click(){ISplitter * splitter=prototype->clone(); //克隆原型splitter->split();}
};
原型方法定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型方法结构
参与者
• Prototype(ISplitter)
— 声明一个克隆自身的接口。
• ConcretePrototype(BinarySplitter、TxtSplitter、PictureSplitter等)
— 实现一个克隆自身的操作。
• Client(MainForm)
— 让一个原型克隆自身从而创建一个新的对象。
再给出chatGPT生成的原型方法的示例
原型方法(Prototype Pattern)是一种创建型设计模式,其主要目的是通过复制现有对象来创建新对象,而不是通过实例化类。这样做可以避免直接使用构造函数创建对象,提高对象的创建效率,同时也使得对象的创建更加灵活。原型方法通常包含一个克隆方法,用于复制对象。
以下是一个简单的C++代码示例,演示了原型方法的实现:
#include <iostream>
#include <string>// 抽象原型类
class Prototype {
public:virtual Prototype* clone() const = 0;virtual void printInfo() const = 0;virtual ~Prototype() {}
};// 具体原型类 A
class ConcretePrototypeA : public Prototype {
public:Prototype* clone() const override {return new ConcretePrototypeA(*this);}void printInfo() const override {std::cout << "Concrete Prototype A" << std::endl;}
};// 具体原型类 B
class ConcretePrototypeB : public Prototype {
public:Prototype* clone() const override {return new ConcretePrototypeB(*this);}void printInfo() const override {std::cout << "Concrete Prototype B" << std::endl;}
};int main() {// 创建原型对象Prototype* prototypeA = new ConcretePrototypeA();Prototype* prototypeB = new ConcretePrototypeB();// 克隆对象Prototype* cloneA = prototypeA->clone();Prototype* cloneB = prototypeB->clone();// 打印信息prototypeA->printInfo();cloneA->printInfo();prototypeB->printInfo();cloneB->printInfo();// 释放资源delete prototypeA;delete cloneA;delete prototypeB;delete cloneB;return 0;
}
在上述示例中,Prototype
是抽象原型类,其中包含了 clone
和 printInfo
纯虚函数。ConcretePrototypeA
和 ConcretePrototypeB
是具体的原型类,分别实现了这两个函数。在 main
函数中,通过创建原型对象,然后克隆对象,最后打印信息,展示了原型方法的基本用法。
适用性
原型方法适用于那些需要频繁创建对象,且对象的创建成本较高或创建过程复杂的情况。通过克隆现有对象来创建新对象,可以提高效率、简化代码,并且灵活应对动态配置和变化。
原型模式和工厂方法的区别
- 创建方式:
- 原型模式通过克隆现有对象来创建新对象。
- 工厂方法模式通过实现工厂方法来由子类决定创建的具体产品。
- 关注点:
- 原型模式关注对象的复制。
- 工厂方法模式关注对象的创建。
- 灵活性:
- 原型模式更加灵活,允许在运行时动态选择复制的对象。
- 工厂方法模式更加固定,创建的对象由具体的子类工厂决定。
在实际应用中,选择使用哪种模式取决于需求和设计目标。原型模式更适合对象创建时变化不大、但需要频繁创建的情况,而工厂方法模式更适合在创建对象的类层次结构变化频繁时。
11 构建器(Builder)
Motivation
在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?
代码示例:建立各种房子,比如砖瓦房,高楼,别墅等房子
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{};// 石头房子的创建,override HouseBuilder里面的流程
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的指针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();}
};int main() {// 创建石头房子的建造者StoneHouseBuilder stoneHouseBuilder;// 创建房子Director,并指定建造者HouseDirector houseDirector(&stoneHouseBuilder);// 构建房子House* stoneHouse = houseDirector.Construct();// 输出结果if (stoneHouse != nullptr) {// 假设 House 类有适当的输出方法// 这里只是简单示例,实际使用时需要根据具体类的实现调用相应的方法std::cout << "Stone House constructed." << std::endl;} else {std::cout << "Failed to construct Stone House." << std::endl;}// 释放资源delete stoneHouse;return 0;
}
上述代码涉及了Builder设计模式,包括以下几个类:
-
House
类:- 表示待构建的产品,即房子。在示例中,
StoneHouse
类是House
的具体实现。
- 表示待构建的产品,即房子。在示例中,
-
HouseBuilder
抽象类:- 定义了构建产品(房子)的抽象接口。具体的建造者(例如
StoneHouseBuilder
)需要继承这个抽象类并实现接口中的方法,以完成具体产品的构建。
- 定义了构建产品(房子)的抽象接口。具体的建造者(例如
-
StoneHouseBuilder
类:- 是
HouseBuilder
的具体实现,负责构建石头房子。它通过实现抽象接口中的方法来完成具体的建造流程。
- 是
-
HouseDirector
类:- 起到指导建造的作用,通过构造函数接收一个具体的建造者对象。通过调用建造者的方法,按照一定的构建流程组织建造者完成产品的构建。
-
main
函数:- 在
main
函数中,创建了StoneHouseBuilder
的实例,然后创建了一个HouseDirector
的实例,并将建造者传递给导演。通过导演的Construct
方法,按照一定的构建流程构建了石头房子,并输出结果。
- 在
总体而言,Builder设计模式的目的是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。在这个示例中,HouseBuilder
抽象类定义了构建房子的接口,StoneHouseBuilder
实现了具体的石头房子构建流程,而 HouseDirector
则负责协调建造者完成整个构建流程。
Builder定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
Builder结构
参与者
• Builder(HouseBuilder)
— 为创建一个Product对象的各个部件指定抽象接口。
• ConcreteBuilder(StoneHouseBuilder等)
— 实现Builder的接口以构造和装配该产品的各个部件。
— 定义并明确它所创建的表示。
— 提供一个检索产品的接口。
• Director(HouseDirector)
— 构造一个使用Builder接口的对象。
• Product(这里没表现出来)
— 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。
— 包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
Builder要点总结
Builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
变化点在哪里,封装哪里—— Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。
在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#) 。
chatGPT给出的builder的例子
以下是一个简单的C++示例代码,演示Builder设计模式:
#include <iostream>
#include <string>// 产品类
class Product {
public:void AddPart(const std::string& part) {parts += part + " ";}void Show() const {std::cout << "Product Parts: " << parts << std::endl;}private:std::string parts;
};// 抽象建造者类
class Builder {
public:virtual void BuildPart1() = 0;virtual void BuildPart2() = 0;virtual Product* GetResult() = 0;
};// 具体建造者类
class ConcreteBuilder : public Builder {
public:ConcreteBuilder() {product = new Product();}void BuildPart1() override {product->AddPart("Part1");}void BuildPart2() override {product->AddPart("Part2");}Product* GetResult() override {return product;}private:Product* product;
};// 指导者类
class Director {
public:Director(Builder* builder) : builder(builder) {}void Construct() {builder->BuildPart1();builder->BuildPart2();}private:Builder* builder;
};int main() {// 创建具体建造者ConcreteBuilder concreteBuilder;// 创建指导者,并传入具体建造者Director director(&concreteBuilder);// 指导者构建产品director.Construct();// 获取最终产品Product* product = concreteBuilder.GetResult();// 展示产品if (product != nullptr) {product->Show();delete product;}return 0;
}
在这个示例中,Product
表示最终构建的复杂对象,Builder
是抽象建造者类,ConcreteBuilder
是具体建造者类,Director
是指导者类。通过指导者调用具体建造者的方法,最终得到构建完成的产品。这个例子中的产品是简单的包含两个部分的字符串,实际应用中可以根据需求定义更复杂的产品结构。
“对象性能”模式
面向对象很好地解决了“抽象”的问题,但是必不可免地要付出 一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。
典型模式
·Singleton
·Flyweight
12 单例模式(Singleton)
Motivation
在软件系统中,经常有一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性,以及良好的效率。
如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
这应该是类设计者的责任,而不是使用者的责任。
单例模式代码举例:把构造函数设置为private
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;
}
单例模式定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式结构
参与者
• Singleton
— 定义一个 Instance 操作,允许客户访问它的唯一实例。 Instance 是一个类操作(即 Smalltalk 中的一个类方法和 C++ 中的一个静态成员函数)。
— 可能负责创建它自己的唯一实例。
协作
• 客户只能通过 Singleton 的 Instance 操作访问一个 Singleton 的实例。
下面是对单例模式双检查锁的介绍
在单例模式中使用双检查锁(DCL,Double-Checked Locking)时,可能会遇到一些问题,特别是在多线程环境下。在C++11之前的版本中,由于缺少内存栅栏(memory barrier)的支持,可能导致DCL失效,从而造成线程安全性问题。
主要问题包括:
-
内存写入和读取的重排序问题:
在一些编译器和硬件架构中,对于指令的执行顺序可能会发生重排序,导致在实例化对象时,成员变量的内存分配可能在对象构造之前,从而使其他线程在获取到非空但尚未构造完全的对象。 -
缺乏内存栅栏:
在C++11之前,C++标准库并没有提供内存栅栏的概念,而DCL依赖于内存栅栏来保证写入和读取的顺序性。缺少内存栅栏可能导致指令乱序执行,使得双检查锁失效。 -
编译器优化问题:
一些编译器可能会对代码进行优化,可能会在保证正确性的情况下对代码进行调整,从而导致DCL失效。
在C++11及以后的版本中,引入了std::atomic
等特性,可以更好地保证内存可见性和顺序性,从而避免了上述问题。因此,如果在C++11及以后的环境中,使用std::atomic
可以解决DCL的一些问题。
// Singleton.h
#pragma once#include <atomic>
#include <mutex>class Singleton {
public:// 获取单例实例的静态方法static Singleton* Instance();private:// 私有构造函数,确保单例不能通过其他方式实例化Singleton() {}private:// 使用std::atomic提供的原子操作,确保多线程环境下的安全性static std::atomic<Singleton*> m_instance;// 用于保护实例化过程的互斥锁static std::mutex m_mutex;
};// Singleton.cpp
#include "Singleton.h"// 初始化静态成员变量
std::atomic<Singleton*> Singleton::m_instance(nullptr);
std::mutex Singleton::m_mutex;Singleton* Singleton::Instance() {// 使用std::atomic加载实例,确保内存可见性Singleton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);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::atomic_thread_fence(std::memory_order_release);// 存储单例对象的指针m_instance.store(tmp, std::memory_order_relaxed);}}return tmp;
}
这里,使用std::atomic
提供的内存顺序和std::atomic_thread_fence
来保证正确的内存可见性和顺序性。在C++11及以后的环境中,这种方式是相对安全的。
13 享元模式(Flyweight)
Motivation
在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价。
如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地适用面向对象的方式来进行操作?
代码举例:,创建字体对象时,同一个key的字体对象只有一个,而不是有很多
// Font.h
#pragma once#include <string>class Font {
private:// 字体对象的唯一标识符std::string key;// 字体对象的状态(可能包含其他属性,这里未具体展示)// ....public:// 构造函数,根据传入的 key 初始化字体对象Font(const std::string& key) : key(key) {// ...}
};// FontFactory.h
#pragma once#include <map>
#include "Font.h"class FontFactory {
private:// 存储已创建的字体对象,key 是字体对象的唯一标识符,value 是对应的字体对象指针std::map<std::string, Font*> fontPool;public:// 获取字体对象Font* GetFont(const std::string& key);// 清理字体池void clear();
};// FontFactory.cpp
#include "FontFactory.h"Font* FontFactory::GetFont(const std::string& key) {// 查找字体池中是否已存在对应 key 的字体对象auto iter = fontPool.find(key);if (iter != fontPool.end()) {// 如果存在,则直接返回已有的字体对象return iter->second;} else {// 如果不存在,则创建新的字体对象,并加入字体池Font* font = new Font(key);fontPool[key] = font;return font;}
}void FontFactory::clear() {// 清理字体池中的所有字体对象for (auto& entry : fontPool) {delete entry.second;}fontPool.clear();
}
享元模式定义
运用共享技术有效地支持大量细粒度的对象。
享元模式结构
参与者
• Flyweight
— 描述一个接口,通过这个接口 flyweight 可以接受并作用于外部状态。
• ConcreteFlyweight
— 实现 Flyweight 接口,并为内部状态(如果有的话)增加存储空间。
ConcreteFlyweight 对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于 ConcreteFlyweight 对象的场景。
• UnsharedConcreteFlyweight
— 并非所有的 Flyweight 子类都需要被共享。 Flyweight 接口使共享成为可能,但它并不强制共享。在 Flyweight 对象结构的某些层次, UnsharedConcreteFlyweight 对象通常将 ConcreteFlyweight 对象作为子节点。
• FlyweightFactory
— 创建并管理 flyweight 对象。
— 确保合理地共享 flyweight。当用户请求一个 flyweight 时, FlyweightFactory 对象提供一个已创建的实例或者创建一个(如果不存在的话)。
• Client
— 维持一个对 flyweight 的引用。
— 计算或存储一个(多个) flyweight 的外部状态。
下图说明如何共享flyweight
chatGPT给出的享元模式的例子
以下是一个简单的 C++ 代码例子,演示了享元模式的实现。这个例子以文本编辑器中的字符格式化为场景。
#include <iostream>
#include <map>// 享元接口
class CharacterFormat {
public:virtual void applyFormat() const = 0;
};// 具体享元类,代表不同的字符格式
class BoldFormat : public CharacterFormat {
public:void applyFormat() const override {std::cout << "Applying Bold Format\n";}
};class ItalicFormat : public CharacterFormat {
public:void applyFormat() const override {std::cout << "Applying Italic Format\n";}
};class UnderlineFormat : public CharacterFormat {
public:void applyFormat() const override {std::cout << "Applying Underline Format\n";}
};// 享元工厂类,负责创建和管理享元对象
class CharacterFormatFactory {
private:std::map<std::string, CharacterFormat*> formatPool;public:CharacterFormat* getFormat(const std::string& key) {auto iter = formatPool.find(key);if (iter != formatPool.end()) {return iter->second;} else {CharacterFormat* format = nullptr;if (key == "Bold") {format = new BoldFormat();} else if (key == "Italic") {format = new ItalicFormat();} else if (key == "Underline") {format = new UnderlineFormat();}formatPool[key] = format;return format;}}
};// 客户端代码
int main() {CharacterFormatFactory formatFactory;// 用户输入的字符格式std::string userInput = "Bold";// 通过享元工厂获取或创建字符格式对象CharacterFormat* format = formatFactory.getFormat(userInput);// 应用字符格式if (format) {format->applyFormat();} else {std::cout << "Invalid Format\n";}// 释放资源delete format;return 0;
}
在这个例子中,CharacterFormat
是享元接口,而 BoldFormat
、ItalicFormat
、UnderlineFormat
是具体的享元类。CharacterFormatFactory
是享元工厂类,负责创建和管理享元对象。在客户端代码中,用户输入字符格式的信息,通过享元工厂获取或创建相应的字符格式对象,并应用该格式。这样,相同的字符格式对象可以被多次共享,减少了资源的重复创建。
要点总结
面向对象很好地解决了抽象性的问题,但是作为一个运行在机器 中的程序实体,我们需要考虑对象的代价问题。Flyweight主要解 决面向对象的代价问题,一般不触及面向对象的抽象性问题。
Flyweight采用对象共享的做法来降低系统中对象的个数,从而降 低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对 象状态的处理。
对象的数量太大从而导致对象内存开销加大——什么样的数量才 算大?这需要我们仔细的根据具体应用情况进行评估,而不能凭空 臆断。
后记
截至2024年1月17日14点48分,学习了Factory Method, Abstract Factory, Prototype, Builder, Singleton, Flyweight模式。下午继续学习。
相关文章:

C++设计模式(李建忠)笔记2
C设计模式(李建忠) 本文是学习笔记,如有侵权,请联系删除。 参考链接 Youtube: C设计模式 Gtihub源码与PPT:https://github.com/ZachL1/Bilibili-plus 豆瓣: 设计模式–可复用面向对象软件的基础 文章目录 C设计模…...

【机器学习】DBSCAN算法
参考链接: https://blog.csdn.net/haveanybody/article/details/113092851 https://www.jianshu.com/p/dd6ce77bfb8a 1 介绍 DBSCAN(Density-Based Spatial Clustering of Applica tion with Noise)算法是于1996年提出的一种简单的、有效的基于密度的聚类算法&…...

Uniapp软件库源码-全新带勋章等
测试环境:php7.1。ng1.2,MySQL 5.6 常见问题: 配置好登录后转圈圈,检查环境及伪静态以及后台创建好应用 上传图片不了,检查php拓展fileinfo 以及public文件权限 App个人主页随机背景图,在前端uitl文件夹里面…...

Microsoft Excel 直方图
Microsoft Excel 直方图 1. 数据示例2. 打开 EXCEL3. settings4. 单击直方图柱,右键“添加数据标签”References 1. 数据示例 2. 打开 EXCEL 数据 -> 数据分析 -> 直方图 3. settings 输入区域样本值、接受区域分类间距,输出选项选择“新工作表组…...

如何录制屏幕视频?让视频制作更简单!
随着数字化时代的来临,录制屏幕视频成为一种常见的传播和教学方式。无论是制作演示文稿、教学视频,还是记录游戏操作,屏幕录制为用户提供了强大而灵活的工具。可是您知道如何录制屏幕视频吗?本文将深入介绍两种常见的屏幕录制方法…...

【JavaEE进阶】 关于应用分层
文章目录 🎋序言🍃什么是应⽤分层🎍为什么需要应⽤分层🍀如何分层(三层架构)🎄MVC和三层架构的区别和联系🌳什么是高内聚低耦合⭕总结 🎋序言 在我们进行项目开发时我们如果一股脑将所有代码都…...

【已解决】c语言const/指针学习笔记
本博文源于笔者正在复习const在左与在右,指针优先级、a,&a,*a的区别。 1、const在左与在右 int const *p const int *p int * const p int const * const p const int * const p* 在const右边,指向的数据不可以改变,可以改变地址 * 在c…...

Android 系统启动过程纪要(基于Android 10)
前言 看过源码的都知道,Launcher系统启动都会经过这三个进程 init ->zygote -> system_server。今天我们就来讲解一下这三个进程以及Launcher系统启动。 init进程 准备Android虚拟机环境:创建和挂载系统文件目录;初始化属性服务&…...

【Docker实用篇】一文入门Docker(4)Docker-Compose
目录 1.Docker-Compose 1.1.初识DockerCompose 1.2.安装DockerCompose 1.2.1 修改文件权限 1.2.2 Base自动补全命令: 1.3部署微服务集群 1.3.1.compose文件 1.3.2.修改微服务配置 1.3.3.打包 1.3.4.拷贝jar包到部署目录 1.3.5.部署 1.Docker-Compose Doc…...

neo4j 图数据库 py2neo 操作 示例代码
文章目录 摘要前置NodeMatcher & RelationshipMatcher创建节点查询获取节点节点有则查询,无则创建创建关系查询关系关系有则查询,无则创建 Cypher语句创建节点 摘要 利用py2neo包,实现把excel表里面的数据,插入到neo4j 图数据…...

从uptime看linux平均负载
从前遇到系统卡顿只会top。。top看不出来怎么搞呢? Linux系统提供了丰富的命令行工具,以帮助用户和系统管理员监控和分析系统性能。在这些工具中,uptime、mpstat和pidstat是非常有用的命令,它们可以帮助你理解系统的平均负载以及资…...

经典数据库练习题及答案
数据表介绍 --1.学生表 Student(SId,Sname,Sage,Ssex) --SId 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 --2.课程表 Course(CId,Cname,TId) --CId 课程编号,Cname 课程名称,TId 教师编号 --3.教师表 Teacher(TId,Tname) --TId 教师编号,Tname 教师姓名 --4.成绩…...

架构篇06-复杂度来源:可扩展性
文章目录 预测变化应对变化小结 复杂度来源前面已经讲了高性能和高可用,今天来聊聊可扩展性。 可扩展性指系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系…...

flowable流程结束触发监听器 flowable获取结束节点 flowable流程结束事件响应监听器
flowable流程结束触发监听器 | flowable流程结束获取结束节点 | flowable流程结束事件响应监听器 下面代码是该监听器是对每个到达结束事件后执行的。 原本的流程定义是如果其中任意某个节点进行了驳回,则直接结束流程。 所以在每个节点的驳回对应的排他网关都设…...

【Python3】【力扣题】389. 找不同
【力扣题】题目描述: 【Python3】代码: 1、解题思路:使用计数器分别统计字符串中的元素和出现次数,两个计数器相减,结果就是新添加的元素。 知识点:collections.Counter(...):字典子类&#x…...

【从0上手cornerstone3D】如何加载nifti格式的文件
在线演示 支持加载的文件格式 .nii .nii.gz 代码实现 npm install cornerstonejs/nifti-volume-loader// ------------- 核心代码 Start------------------- // 注册一个nifti格式的加载器 volumeLoader.registerVolumeLoader("nifti",cornerstoneNiftiImageVolu…...

c# 学习笔记 - 异步编程
文章目录 1. 异步编程介绍1.1 简单介绍1.2 async/await 使用1.3 Task/Task<TResult> 对象 2. 样例2.1 迅速启动所有任务,仅当需要结果才等待任务执行2.2 使用 await 调用异步方法,即使这个异步方法内有 await 也不会同时执行回调和向下执行操作(必…...

设置了uni.chooseLocation,小程序中打不开
设置了uni.chooseLocation,在小程序打不开,点击没反应,地图显现不出来; 解决方案: 1.Hbuilder——微信开发者工具路径没有配置 打开工具——>设置 2.微信小程序服务端口没有开 解决方法:打开微信开发…...

spring retry 配置及使用
spring retry 配置及使用 接口或功能因外界异常导致失败后进行重推机制 依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.1.RELEASE</version></p…...

uni-app的组件(二)
多项选择器checkbox-group 多项选择器,内部由多个 checkbox 组成。 <checkbox-group><checkbox checked color"red" value"1"></checkbox> 篮球<!-- disabled:是否禁用 --><checkbox disabled color"rgba(0,0…...

项目开发中安全问题以及解决办法——客户传进来的数据不可信
用户传进来的数据是不可信的,比如下面这种情况下: PostMapping("/order") public void wrong(RequestBody Order order) { this.createOrder(order); } Data public class Order { private long itemId; //商品ID private BigDecimal ite…...

解决springboot启动报Failed to start bean ‘subProtocolWebSocketHandler‘;
解决springboot启动报 Failed to start bean subProtocolWebSocketHandler; nested exception is java.lang.IllegalArgumentException: No handlers 问题发现问题解决 问题发现 使用springboot整合websocket,启动时报错,示例代码: EnableW…...

什么是技术架构?架构和框架之间的区别是什么?怎样去做好架构设计?(一)
什么是技术架构?架构和框架之间的区别是什么?怎样去做好架构设计?(一)。 在软件行业,对于什么是架构,都有很多的争论,每个人都有自己的理解。在不同的书籍上, 不同的作者, 对于架构的定义也不统一, 角度不同, 定义不同。 一、架构是什么 Linux 有架构,MySQL 有架构,J…...

【多线程】认识Thread类及其常用方法
📄前言: 本文是对以往多线程学习中 Thread类 的介绍,以及对其中的部分细节问题进行总结。 文章目录 一. 线程的 创建和启动🍆1. 通过继承 Thread 类创建线程🍅2. 通过实现 Runnable 接口创建线程🥦3. 其他方…...

多用户商业版 whisper 2.1在线搭建教程
1. 准备工作 购买许可证:确保你已经购买了足够数量的用户许可证,以便所有员工或客户都能使用软件。系统要求:检查你的服务器和客户端计算机是否满足软件的最低系统要求。网络配置:确保你的网络环境(如防火墙、路由器等…...

HEXO搭建个人博客
Hexo是一款基于Node.js的静态博客框架,可以生成静态网页托管在GitHub上。中文文档见HEXO 配置环境 安装Git:下载并安装Git 检查git是否正确安装: git --version 安装Node.js:Node.js 为大多数平台提供了官方的安装程序。注意安装…...

Spring MVC学习之——RequestMapping注解
RequestMapping注解 作用 用于建立请求URL和处理请求方法之间的对应关系。 属性 value:指定请求的实际地址,可以是一个字符串或者一个字符串列表。 value可以不写,直接在括号中写,默认就是value值 RequestMapping(value“/hel…...

鸿蒙原生应用/元服务开发-延迟任务开发实现(二)
一、接口说明 接口名接口描述startWork(work: WorkInfo): void;申请延迟任务stopWork(work: WorkInfo, needCancel?: boolean): void;取消延迟任务getWorkStatus(workId: number, callback: AsyncCallback>): void;获取延迟任务状态(Callback形式)g…...

机器学习在什么场景下最常用-九五小庞
机器学习在多个场景中都有广泛的应用,下面是一些常见的应用场景: 自然语言处理(NLP):如语音识别、自动翻译、情感分析、垃圾邮件过滤等。数据挖掘和分析:如市场分析、用户画像、推荐系统、欺诈检测等。智能…...

利用IP应用场景API识别真实用户
引言 在当今数字化时代,随着互联网的普及和应用的广泛,验证用户身份的重要性变得越来越突出。在许多场景中,特别是在涉及安全性、用户体验以及个人隐私保护方面,确定用户的真实身份至关重要。而IP应用场景API则是一种强大的工具&…...