C++设计模式(李建忠)笔记2

news2024/12/26 23:47:37

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(); //多态new
        
        splitter->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 是抽象原型类,其中包含了 cloneprintInfo 纯虚函数。ConcretePrototypeAConcretePrototypeB 是具体的原型类,分别实现了这两个函数。在 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设计模式,包括以下几个类:

  1. House 类:

    • 表示待构建的产品,即房子。在示例中,StoneHouse 类是 House 的具体实现。
  2. HouseBuilder 抽象类:

    • 定义了构建产品(房子)的抽象接口。具体的建造者(例如 StoneHouseBuilder)需要继承这个抽象类并实现接口中的方法,以完成具体产品的构建。
  3. StoneHouseBuilder 类:

    • HouseBuilder 的具体实现,负责构建石头房子。它通过实现抽象接口中的方法来完成具体的建造流程。
  4. HouseDirector 类:

    • 起到指导建造的作用,通过构造函数接收一个具体的建造者对象。通过调用建造者的方法,按照一定的构建流程组织建造者完成产品的构建。
  5. 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);//获取内存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

— 定义一个 Instance 操作,允许客户访问它的唯一实例。 Instance 是一个类操作(即 Smalltalk 中的一个类方法和 C++ 中的一个静态成员函数)。

— 可能负责创建它自己的唯一实例。

协作

• 客户只能通过 Singleton 的 Instance 操作访问一个 Singleton 的实例。

下面是对单例模式双检查锁的介绍

在单例模式中使用双检查锁(DCL,Double-Checked Locking)时,可能会遇到一些问题,特别是在多线程环境下。在C++11之前的版本中,由于缺少内存栅栏(memory barrier)的支持,可能导致DCL失效,从而造成线程安全性问题。

主要问题包括:

  1. 内存写入和读取的重排序问题:
    在一些编译器和硬件架构中,对于指令的执行顺序可能会发生重排序,导致在实例化对象时,成员变量的内存分配可能在对象构造之前,从而使其他线程在获取到非空但尚未构造完全的对象。

  2. 缺乏内存栅栏:
    在C++11之前,C++标准库并没有提供内存栅栏的概念,而DCL依赖于内存栅栏来保证写入和读取的顺序性。缺少内存栅栏可能导致指令乱序执行,使得双检查锁失效。

  3. 编译器优化问题:
    一些编译器可能会对代码进行优化,可能会在保证正确性的情况下对代码进行调整,从而导致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 是享元接口,而 BoldFormatItalicFormatUnderlineFormat 是具体的享元类。CharacterFormatFactory 是享元工厂类,负责创建和管理享元对象。在客户端代码中,用户输入字符格式的信息,通过享元工厂获取或创建相应的字符格式对象,并应用该格式。这样,相同的字符格式对象可以被多次共享,减少了资源的重复创建。

要点总结

面向对象很好地解决了抽象性的问题,但是作为一个运行在机器 中的程序实体,我们需要考虑对象的代价问题。Flyweight主要解 决面向对象的代价问题,一般不触及面向对象的抽象性问题。

Flyweight采用对象共享的做法来降低系统中对象的个数,从而降 低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对 象状态的处理。

对象的数量太大从而导致对象内存开销加大——什么样的数量才 算大?这需要我们仔细的根据具体应用情况进行评估,而不能凭空 臆断。

后记

截至2024年1月17日14点48分,学习了Factory Method, Abstract Factory, Prototype, Builder, Singleton, Flyweight模式。下午继续学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1394847.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vite和webpack的区别和作用

前言 Vite 和 Webpack 都是现代化的前端构建工具&#xff0c;它们可以帮助开发者优化前端项目的构建和性能。虽然它们的目标是相似的&#xff0c;但它们在设计和实现方面有许多不同之处。 一、Vite详解和作用 vite 是什么 vite —— 一个由 vue 作者尤雨溪开发的 web 开发工…

第二百七十三回

文章目录 1. 概念介绍2. 方法与信息2.1 获取方法2.2 详细信息 3. 示例代码4. 内容总结 我们在上一章回中介绍了"蓝牙综合示例"相关的内容&#xff0c;本章回中将介绍如何获取设备信息.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中获…

鸿蒙开发之手势Pan

Entry Component struct OfficialPanGesturePage {State message: string 默认只左右移动State offsetX: number 0State offsetY: number 0State positionX: number 0State positionY: number 0//默认pan的参数&#xff0c;1根手指&#xff0c;左右方向private panOption:…

提纲框架写作方法

论文提纲 论文提纲的意义 有利于检查构思有利于调整修改和写作 拟定提纲的目的 拟标题写总论点做总安排&#xff1a;几个方面&#xff0c;什么顺序做下位论点&#xff1a;每个项目的下位论点&#xff0c;直到段一级&#xff0c;写段的论点句考虑各段安排&#xff0c;把材料…

Visual Studio 与 SQL Server 常见报错解决方案(工作向)

前言 这篇文章从今天创建开始&#xff0c;会一直更新下去&#xff0c;以后遇到常见但是比较容易解决的报错会在本文进行更新&#xff0c;有需要的朋友可以收藏再看 目录 Visual Studio lc.exe已退出&#xff0c;代码为-1无法导入以下密钥文件xxx.pfx&#xff0c;该密钥文件…

RFID涉密文件载体管控系统

1.1 系统简介 RFID涉密文件载体管控系统是一种基于远距离射频识别技术的解决方案&#xff0c;通过非接触式采集射频卡的信息&#xff0c;实现对涉密文件载体的自动识别和监管&#xff0c;该系统集成了计算机软硬件、信息采集处理、数据传输、网络通讯、机械电子、自动控制和智…

uniapp踩坑之项目:canvas第一次保存是空白图片

在ctx.draw()回调生成图片&#xff0c;参考canvasToTempFilePath接口文档 // data imgFilePath: null,// 缓存二维码图片canvas路径//js // 首先在draw&#xff08;&#xff09;里进行本地存储 ...... ctx.draw(false, () >{uni.canvasToTempFilePath({ // 把画布转化成临时…

算法练习-反转一个单链表(思路+流程图+代码)

难度参考 难度&#xff1a;简单 分类&#xff1a;链表 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。以下内容均为个人笔记&#xff0c;旨在督促自己认真学习。 题目 反转一个单链表&#xff08;不带头节点&#xff09; …

hub汉语有轮毂的意思吗?

问题描述&#xff1a;hub汉语有轮毂的意思吗&#xff1f; 问题解答&#xff1a; 是的&#xff0c;"hub"&#xff08;中文翻译为"轮毂"&#xff09;是指机械装置中的一个中心部分&#xff0c;通常用于连接或支持其他部分。在车辆的轮胎系统中&#xff0c;…

STC8H8K蓝牙智能巡线小车——1. 环境搭建(基于RTX51操作系统)

1. 基本介绍 开发环境准备&#xff1a;Keil uVision5 烧录软件&#xff1a;STC-ISP&#xff08;V6.92A&#xff09; 芯片&#xff1a; STC8H8K64U-45I-LQFP64 芯片引脚&#xff1a; 2.创建项目 打开Keil&#xff0c;点击【Project】&#xff0c;选择【new uVersion proje…

C语言——大头记单词

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 每一发奋努力的背后&#xff0c;必有加…

【​电力电子在电力系统中的应用​】6 滞环电流控制的PWM整流器 + STATCOM整流器 + APF仿真

【仅供参考】 【2023.06西南交大电力电子在电力系统中的应用】 目录 步骤一&#xff1a;基于滞环电流控制的PWM整流器仿真 1.1 仿真要求 1.2 仿真电路原理及设计 1.2.1 主电路的搭建 1.2.2 控制电路的搭建 1.3 波形分析 步骤二&#xff1a;从PWM整流器到STATCOM仿真 2…

ARM day5、day6 硬件编程

一、硬件 fs4412 sd卡 串口线 电源 二、根据原理图点灯 1、确定需求&#xff1a; 点灯&#xff08;亮 or 灭&#xff09; 2、查看原理图 2.1 外设原理图 devboard 查找LED2->CHG_COK(核心板) 2.2 核心板原理图 coreboard 查找CHG_COK->XEINT23/KP_ROW7/ALV_DBG…

Redis分布式锁存在的问题以及解决方式

☆* o(≧▽≦)o *☆嗨~我是小奥&#x1f379; &#x1f4c4;&#x1f4c4;&#x1f4c4;个人博客&#xff1a;小奥的博客 &#x1f4c4;&#x1f4c4;&#x1f4c4;CSDN&#xff1a;个人CSDN &#x1f4d9;&#x1f4d9;&#x1f4d9;Github&#xff1a;传送门 &#x1f4c5;&a…

ROS---传感器集成

ROS—传感器集成 对于ROS机器人&#xff0c;如果想让其启动&#xff0c;我们需要逐一启动底盘控制与激光雷达&#xff0c;操作有点冗余。并且如果只是简单启动这些节点&#xff0c;那么在在 rviz 中显示时&#xff0c;会发现出现了TF转换异常&#xff0c;比如参考坐标系设置为o…

如何设置Windows 11的WSL系统用作备用桌面环境

如何设置Windows 11的WSL系统用作开发环境&#xff08;含Astrill设置&#xff09; 如何设置Windows 11的WSL系统用作备用桌面环境 引子&#xff1a; 2023年末&#xff0c;公司突然遭遇网络攻击&#xff0c;调整了防火墙设置&#xff0c;连接VPN用来飞X软件&#xff0c;与原来…

JAVA基础----String类型的简单介绍

文章目录 1. String类的重要性2. 常用方法2.1 字符串构造2.2 String对象的比较2.3 字符串查找2.4 转化2.5 字符串替换2.6 字符串拆分2.7 字符串截取2.8 其他操作方法2.9 字符串的不可变性2.10 字符串修改2.11 借助StringBuffer 和 StringBuilder 观察String进行修改的效率 3. S…

实时语音多实例实现设计方案(服务端)

1.端上接入协议 如何自行开发代码访问阿里语音服务_智能语音交互(ISI)-阿里云帮助中心 2.接口修改结果逻辑及端上调用步骤 阿里client server交互流程图&#xff1a; 阿里语音接收识别结果&#xff1a; begin_time time 含义 客户端循环发送语音数据&#xff0c;持续接收…

vue学习,使用provide/inject通信

提示&#xff1a;组件的provide&#xff0c;可以被其内所有层级的组件&#xff0c;通过inject引用 文章目录 前言一、通信组件二、效果三、参考文档总结 前言 需求&#xff1a;使用provide/inject通信 一、通信组件 1、AA.vue <template><div class"test"…

【开发实践】前端jQuery+gif图片实现载入界面

一、需求分析 载入界面&#xff08;Loading screen&#xff09;是指在计算机程序或电子游戏中&#xff0c;当用户启动应用程序或切换到新的场景时&#xff0c;显示在屏幕上的过渡界面。它的主要作用是向用户传达程序正在加载或准备就绪的信息&#xff0c;以及提供一种视觉上的反…