结构型设计模式

目录

单例模式

  • 定义:
    单例模式保证某个类只有一个实例,并提供全局访问点。
  • 背景
    有些系统中某个类的实例需要全局唯一,如日志管理、配置文件读取等。通过单例模式,我们可以避免频繁的创建实例,并保证系统的一致性和资源的节约。
  • 要点:
    • 唯一性:确保类只有一个实例。
    • 全局访问点:提供一个方法来访问这个唯一实例。
  • 本质:
    单例模式的核心是通过私有化构造函数来避免外部创建多个实例,同时通过静态方法控制实例的创建。
  • 结构图:
cpp 复制代码
+-------------------+
|     Singleton     |
|-------------------|
| - instance: Singleton  |
|-------------------|
| + getInstance()   |
+-------------------+

C++代码示例:

cpp 复制代码
#include <iostream>
using namespace std;

class Singleton {
private:
    static Singleton* instance;  // 唯一实例指针
    Singleton() {}  // 私有化构造函数,防止外部创建实例

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

    void showMessage() {
        cout << "Hello, Singleton!" << endl;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;

int main() {
    // 获取单例实例并调用方法
    Singleton* singleton = Singleton::getInstance();
    singleton->showMessage();
    return 0;
}

优点:

  • 保证全局唯一性,节省内存。
  • 提供全局访问点,便于管理。

缺点:

  • 难以进行单元测试。
  • 可能会引发全局状态问题。

工厂方法

  • 定义:
    工厂模式通过定义一个接口用于创建对象,但将实例化的过程推迟到子类中进行。
  • 背景:
    当系统需要创建大量的对象,并且这些对象的创建过程较为复杂时,工厂模式可以简化对象的创建过程,避免创建逻辑与业务逻辑混合。
  • 要点:
    • 接口化:工厂方法定义一个接口,具体的实现类决定如何创建对象。
    • 解耦:客户端代码只需依赖工厂接口,而不需要依赖具体类。
  • 本质:
    工厂模式的核心是将对象的创建过程抽象化,避免直接使用构造函数创建对象,从而提高了代码的扩展性。

结构图:

cpp 复制代码
+-----------------+
|    Product     |
+-----------------+
        ^
        |
+-------------------+     +-------------------+
| ConcreteProductA  |     | ConcreteProductB  |
+-------------------+     +-------------------+
        |
+------------------+
|    Creator      |
+------------------+
        ^
        |
+-------------------+
| ConcreteCreator  |
+-------------------+

C++代码示例:

cpp 复制代码
#include <iostream>
using namespace std;

// 产品接口
class Product {
public:
    virtual void operation() = 0;  // 纯虚函数
};

// 具体产品类A
class ConcreteProductA : public Product {
public:
    void operation() override {
        cout << "Operation from Product A" << endl;
    }
};

// 具体产品类B
class ConcreteProductB : public Product {
public:
    void operation() override {
        cout << "Operation from Product B" << endl;
    }
};

// 工厂类
class Creator {
public:
    virtual Product* factoryMethod() = 0;  // 工厂方法
};

// 具体工厂类A
class ConcreteCreatorA : public Creator {
public:
    Product* factoryMethod() override {
        return new ConcreteProductA();  // 创建具体产品A
    }
};

// 具体工厂类B
class ConcreteCreatorB : public Creator {
public:
    Product* factoryMethod() override {
        return new ConcreteProductB();  // 创建具体产品B
    }
};

int main() {
    Creator* creatorA = new ConcreteCreatorA();
    Product* productA = creatorA->factoryMethod();
    productA->operation();

    Creator* creatorB = new ConcreteCreatorB();
    Product* productB = creatorB->factoryMethod();
    productB->operation();

    delete productA;
    delete productB;
    delete creatorA;
    delete creatorB;

    return 0;
}

优点:

  • 解耦客户端与产品的创建过程。
  • 易于扩展,新增产品时只需要扩展新的具体工厂类。

缺点:

  • 如果产品的种类非常多,工厂类会膨胀。

抽象工厂

  • 定义:
    抽象工厂模式提供一个接口,用于创建一系列相关或相互依赖的对象,而不指定具体的类。
  • 背景:
    当系统需要创建的对象不仅仅是一个,而且是多个对象组成的产品族时,抽象工厂模式非常有用。
  • 要点:
    • 产品族:同一产品族中的产品是相关的。
    • 接口化:提供一个抽象的接口,不指定具体产品类。
  • 本质:
    通过抽象工厂模式,可以保证同一系列的产品是相互兼容的。

结构图:

cpp 复制代码
+-------------------+
| AbstractFactory   |
+-------------------+  
        ^
        |
+-------------------+         +-------------------+
| ConcreteFactoryA  |         | ConcreteFactoryB  |
+-------------------+         +-------------------+
        |                           |
+------------------+       +------------------+
| ProductA         |       | ProductB         |
+------------------+       +------------------+

C++代码示例:

cpp 复制代码
#include <iostream>
using namespace std;

// 抽象产品A
class ProductA {
public:
    virtual void operationA() = 0;
};

// 抽象产品B
class ProductB {
public:
    virtual void operationB() = 0;
};

// 具体产品A1
class ConcreteProductA1 : public ProductA {
public:
    void operationA() override {
        cout << "ProductA1 operation" << endl;
    }
};

// 具体产品A2
class ConcreteProductA2 : public ProductA {
public:
    void operationA() override {
        cout << "ProductA2 operation" << endl;
    }
};

// 具体产品B1
class ConcreteProductB1 : public ProductB {
public:
    void operationB() override {
        cout << "ProductB1 operation" << endl;
    }
};

// 具体产品B2
class ConcreteProductB2 : public ProductB {
public:
    void operationB() override {
        cout << "ProductB2 operation" << endl;
    }
};

// 抽象工厂
class AbstractFactory {
public:
    virtual ProductA* createProductA() = 0;
    virtual ProductB* createProductB() = 0;
};

// 具体工厂A
class ConcreteFactoryA : public AbstractFactory {
public:
    ProductA* createProductA() override {
        return new ConcreteProductA1();  // 创建产品A1
    }

    ProductB* createProductB() override {
        return new ConcreteProductB1();  // 创建产品B1
    }
};

// 具体工厂B
class ConcreteFactoryB : public AbstractFactory {
public:
    ProductA* createProductA() override {
        return new ConcreteProductA2();  // 创建产品A2
    }

    ProductB* createProductB() override {
        return new ConcreteProductB2();  // 创建产品B2
    }
};

int main() {
    AbstractFactory* factoryA = new ConcreteFactoryA();
    ProductA* productA = factoryA->createProductA();
    ProductB* productB = factoryA->createProductB();

    productA->operationA();
    productB->operationB();

    delete productA;
    delete productB;
    delete factoryA;

    AbstractFactory* factoryB = new ConcreteFactoryB();
    productA = factoryB->createProductA();
    productB = factoryB->createProductB();

    productA->operationA();
    productB->operationB();

    delete productA;
    delete productB;
    delete factoryB;

    return 0;
}

优点:

  • 使得创建产品的过程与实际使用解耦,易于替换不同产品族。
  • 提供了一致的接口,保证产品族的一致性。

缺点:

  • 需要创建多个工厂类,增加了复杂性。

责任链

  • 定义:
    责任链模式将多个处理对象按顺序连接起来,客户端将请求发送给链中的第一个对象,每个对象都可以处理请求或将请求传递给下一个处理对象。
  • 背景:
    在某些场景中,多个对象可能会处理一个请求,但没有必要指定哪一个对象来处理它。例如,事件处理链、权限检查等。
  • 要点:
    • 链式处理:请求沿着链传递,直到有处理者处理它为止。
    • 解耦:客户端不需要知道请求被哪个处理者处理。
  • 本质:
    通过构建一条责任链,处理过程由多个对象共同完成。
  • 结构图:
cpp 复制代码
+------------------+       +------------------+       +------------------+
|    Handler      |  ---> | ConcreteHandlerA |  ---> | ConcreteHandlerB |
+------------------+       +------------------+       +------------------+

C++代码示例:

cpp 复制代码
#include <iostream>
using namespace std;

// 处理者基类
class Handler {
protected:
    Handler* nextHandler;

public:
    Handler() : nextHandler(nullptr) {}
    virtual void handleRequest(int request) = 0;

    void setNextHandler(Handler* handler) {
        nextHandler = handler;
    }
};

// 具体处理者A
class ConcreteHandlerA : public Handler {
public:
    void handleRequest(int request) override {
        if (request == 1) {
            cout << "Handler A handles request " << request << endl;
        } else if (nextHandler) {
            nextHandler->handleRequest(request);
        }
    }
};

// 具体处理者B
class ConcreteHandlerB : public Handler {
public:
    void handleRequest(int request) override {
        if (request == 2) {
            cout << "Handler B handles request " << request << endl;
        } else if (nextHandler) {
            nextHandler->handleRequest(request);
        }
    }
};

int main() {
    ConcreteHandlerA* handlerA = new ConcreteHandlerA();
    ConcreteHandlerB* handlerB = new ConcreteHandlerB();

    handlerA->setNextHandler(handlerB);

    // 测试
    handlerA->handleRequest(1);  // Handler A handles request 1
    handlerA->handleRequest(2);  // Handler B handles request 2

    delete handlerA;
    delete handlerB;

    return 0;
}

优点:

  • 处理者之间解耦,扩展新的处理者简单。
  • 可以动态修改责任链。

缺点:

  • 请求可能无法得到处理。
  • 调试和追踪链中的请求流可能比较困难。

装饰器

  • 定义:
    装饰器模式允许动态地给一个对象添加一些额外的职责,而不改变其结构。
  • 背景:
    当我们需要在不修改原始对象的基础上增加功能时,装饰器模式是非常有效的。它通常用于功能的扩展,如给文件流增加压缩、加密等功能。
  • 要点:
    • 组合:通过将装饰器类与被装饰对象组合来增强其功能。
    • 灵活性:可以按需添加功能,而不影响其他对象。
  • 本质:
    通过创建装饰器类来包装原始对象,动态添加功能。

结构图:

cpp 复制代码
+------------------+       +-------------------+
|   Component     |  <--- |   ConcreteComponent |
+------------------+       +-------------------+
       ^
       |
+-----------------+       +-------------------+
| Decorator      | <---- | ConcreteDecoratorA |
+-----------------+       +-------------------+

C++代码示例:

cpp 复制代码
#include <iostream>
using namespace std;

// 基础组件
class Coffee {
public:
    virtual double cost() = 0;
};

// 具体组件:普通咖啡
class SimpleCoffee : public Coffee {
public:
    double cost() override {
        return 5.0;
    }
};

// 装饰器基类
class CoffeeDecorator : public Coffee {
protected:
    Coffee* coffee;

public:
    CoffeeDecorator(Coffee* coffee) : coffee(coffee) {}

    double cost() override {
        return coffee->cost();
    }
};

// 具体装饰器:加牛奶
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}

    double cost() override {
        return coffee->cost() + 1.5;
    }
};

// 具体装饰器:加糖
class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}

    double cost() override {
        return coffee->cost() + 0.5;
    }
};

int main() {
    Coffee* coffee = new SimpleCoffee();
    cout << "Cost of Simple Coffee: " << coffee->cost() << endl;

    // 加牛奶
    coffee = new MilkDecorator(coffee);
    cout << "Cost of Milk Coffee: " << coffee->cost() << endl;

    // 加糖
    coffee = new SugarDecorator(coffee);
    cout << "Cost of Milk Coffee with Sugar: " << coffee->cost() << endl;

    delete coffee;
    return 0;
}

优点:

  • 动态地增加对象的功能,避免了继承的复杂性。
  • 增强功能时不会影响原对象。

缺点:

  • 使用多个装饰器时,代码的结构可能变得复杂。
  • 可能导致大量小类的创建。

组合模式

  • 定义:
    组合模式将对象组合成树形结构,以表示"部分-整体"的层次结构。组合模式使得客户端可以统一对待单个对象和组合对象。
  • 背景:
    在处理类似树形结构的对象时,如文件系统、UI组件等,组合模式能够帮助我们统一对待单个元素和由元素组成的容器。
  • 要点:
    • 部分-整体结构:组合模式适用于包含部分和整体的结构。
    • 统一接口:无论是单个对象还是组合对象,都可以通过统一接口来操作。
  • 本质:
    通过树形结构来管理对象的部分和整体,使得客户端不需要关心对象的具体类型。

结构图:

cpp 复制代码
+-------------------+
|      Component    |
+-------------------+
        ^
        |
+-------------------+       +-------------------+
|    Leaf           |       |    Composite      |
+-------------------+       +-------------------+
                          | +add(Component* comp) |
                          | +remove(Component* comp) |
                          +-------------------+

C++代码示例:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

// 基础组件类
class Component {
public:
    virtual void operation() = 0;
};

// 叶子类
class Leaf : public Component {
public:
    void operation() override {
        cout << "Leaf operation" << endl;
    }
};

// 组合类
class Composite : public Component {
private:
    vector<Component*> children;

public:
    void add(Component* comp) {
        children.push_back(comp);
    }

    void remove(Component* comp) {
        children.erase(remove(children.begin(), children.end(), comp), children.end());
    }

    void operation() override {
        for (auto* child : children) {
            child->operation();
        }
    }
};

int main() {
    Leaf* leaf1 = new Leaf();
    Leaf* leaf2 = new Leaf();
    
    Composite* composite = new Composite();
    composite->add(leaf1);
    composite->add(leaf2);

    composite->operation();  // 输出: Leaf operation  Leaf operation

    delete leaf1;
    delete leaf2;
    delete composite;

    return 0;
}

优点:

  • 客户端代码统一,简化操作。
  • 树形结构扩展方便。

缺点:

  • 如果层次过深,可能会使得操作变得复杂。

内存屏障

内存屏障(又称为内存栅栏,Memory Barrier)是计算机体系结构中一种重要的同步原语,它用于控制不同线程(或 CPU 核心)之间的内存访问顺序。内存屏障是硬件层面和编译器层面实现线程同步的一个机制。

内存屏障的定义

内存屏障是一个指令,它能够强制计算机系统的某些操作按特定的顺序执行。它并不会直接影响计算的结果,而是通过确保内存操作的顺序,从而保证程序的正确性。具体来说,它可以用于阻止对内存的重排序(Reordering),确保内存操作的顺序满足程序的要求。

为什么需要内存屏障?

在现代多核处理器中,乱序执行(Out-of-Order Execution)和 指令重排序(Instruction Reordering)是提高性能的常见技术。处理器为了提高效率,会对指令进行重排序。然而,这样的优化可能会打破多线程程序中对内存访问顺序的假设,从而导致数据竞争、内存可见性问题等并发错误。

内存屏障可以确保在特定条件下,内存操作的顺序不会被处理器或编译器重排序,从而保持多线程程序的正确性。

内存屏障的工作原理

内存屏障通过影响 编译器处理器 对内存操作的执行顺序来保证线程间的同步。

  • 编译器层面的屏障:告诉编译器不要在屏障前后的内存操作中进行重排序。
  • 处理器层面的屏障:确保 CPU 在执行屏障前后的内存操作时,按照指定的顺序执行。

内存屏障的类型

内存屏障的种类可以根据它们控制的内存操作顺序不同来区分:

  • 全屏障 (Full Barrier,也叫做 LFENCE 或 SFENCE)
    • 全屏障强制确保屏障前的所有操作在屏障后的所有操作之前执行。这通常用于防止指令重排序。
  • 读屏障 (Read Barrier)
    • 读屏障确保屏障前的所有读操作完成后,才能执行屏障后的操作。即,屏障前的读操作不会被重排到屏障后的操作之前。
  • 写屏障 (Write Barrier)
    • 写屏障确保屏障前的所有写操作完成后,才能执行屏障后的操作。即,屏障前的写操作不会被重排到屏障后的操作之前。
  • 释放屏障 (Release Barrier)
    • 释放屏障保证所有屏障之前的写操作在屏障之后的写操作之前执行。它通常用于同步操作,确保共享数据被写入共享内存之后,其他线程可以看到这些数据。
  • 获取屏障 (Acquire Barrier)
    • 获取屏障保证所有屏障之后的读操作在屏障之前的读操作之前执行。它通常与释放屏障配合使用,用于多线程同步。
  • 顺序一致性屏障 (Sequential Consistency Barrier)
    • 这个屏障保证程序内的操作顺序完全一致,即所有线程对内存的读写操作的顺序都是一致的。

内存屏障的例子

在多核处理器上,通常使用内存屏障来确保两个线程之间的内存操作按照预期顺序发生。以下是一些实际的使用场景:

  • 写后读(Write-Read)同步

    • 假设有两个线程,一个线程先执行写操作,另一个线程进行读操作。为了确保读操作能看到写操作的最新结果,可以在写操作后使用 写屏障,确保写操作完成后,其他线程才可以读取内存。
  • 顺序一致性

    • 如果一个线程对某个共享变量进行写操作,另一个线程在其后进行读操作,为了确保第二个线程读到最新值,可以使用 释放屏障(Release Barrier) 和 获取屏障(Acquire Barrier) 来同步它们之间的内存操作。
  • 多线程与原子操作

    • 在多线程程序中,涉及到原子操作时,如果操作需要对内存进行读写,并且要求特定顺序,那么可以使用内存屏障来保证内存操作按顺序进行。

C++ 中的内存屏障

  • C++11 引入了 std::atomic 类型和原子操作,提供了一些内存顺序选项,这些选项通过内存屏障来实现:
cpp 复制代码
#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> x(0);

void writer() {
    x.store(42, std::memory_order_release); // 写操作并释放屏障
}

void reader() {
    int value = x.load(std::memory_order_acquire); // 读操作并获取屏障
    std::cout << "Reader sees value: " << value << std::endl;
}

int main() {
    std::thread t1(writer);
    std::thread t2(reader);
    
    t1.join();
    t2.join();
    
    return 0;
}

在这个例子中:

  • store 使用了 std::memory_order_release,保证在 x 被写入之后,所有之前的操作在屏障之后的线程中可见。
  • load 使用了 std::memory_order_acquire,确保在读取 x 后,之前的操作是可见的。

硬件级别的内存屏障

不同的处理器架构提供不同的内存屏障指令。以下是一些常见架构中的内存屏障指令:

  • x86 架构:MFENCE,LFENCE,SFENCE
  • ARM 架构:DMB,DSB,ISB(数据内存屏障,数据同步屏障,指令同步屏障)。
  • PowerPCSync

总结

内存屏障是并发编程中的一种重要工具,它帮助我们控制多线程程序中的内存访问顺序,避免由于处理器的乱序执行和指令重排序所带来的问题。理解内存屏障及其在现代处理器架构中的使用是编写高效且正确的并发程序的关键。

在 C++ 中,内存屏障通常通过原子操作和内存顺序(如std::memory_order_acquire、std::memory_order_release 等)来实现,确保在多线程环境下的正确同步。

相关推荐
froginwe113 分钟前
Perl 特殊变量
开发语言
stormjun33 分钟前
基于 Python Django 的农产品销售系统的研究与实现
开发语言·python·django·农产品·农产品销售
光影少年43 分钟前
js原型和原型链
开发语言·javascript·原型模式
carrie呀carrie1 小时前
HarmonyOS:删除多层ForEach循环渲染的复杂数据而导致的一系列问题
开发语言·harmonyos·鸿蒙
Oneforlove_twoforjob1 小时前
【Java基础面试题044】使用new String(“哈哈“)语句会创建几个对象?
java·开发语言
eqwaak01 小时前
爬虫自动化(DrissionPage)
开发语言·人工智能·爬虫·python·自动化·pip
坊钰1 小时前
【Java 数据结构】LinkedList 类 和 模拟实现链表
java·开发语言·数据结构·学习·算法·链表
胡尔摩斯.1 小时前
SpringMVC
java·开发语言·后端·spring·代理模式
淘小白_TXB21961 小时前
讯飞星火智能生成PPTAPi接口说明文档 python示例demo
开发语言·python·powerpoint
橘颂TA1 小时前
【C++】数据结构 单链表的实现(企业存储用户数据的实现)
开发语言·数据结构·c++