五种常见的设计模式(单例模式、简单工厂模式、观察者模式、状态模式、适配器模式)的详细解释和C++示例代码

以下为五种常见的设计模式(单例模式、简单工厂模式、观察者模式、状态模式、适配器模式)的详细解释和C++示例代码。每种模式都会以一篇完整文章的形式呈现,包括设计思想、适用场景、优缺点、代码实现以及代码注释。内容将尽量清晰、详尽,并以中文表述。


1. 单例模式 (Singleton Pattern)

概述

单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。单例模式适用于需要全局唯一对象的场景,例如日志管理器、配置文件管理器或数据库连接池。

设计思想

单例模式的核心思想是:

  • 限制类的实例化,防止通过new创建多个对象。

  • 提供一个静态方法作为全局访问点,返回唯一实例。

  • 确保线程安全(在多线程环境中,防止多个线程同时创建实例)。

适用场景

  • 需要全局唯一实例的场景,例如:

    • 日志系统:全局只有一个日志对象,避免重复写入。

    • 配置管理:全局共享一份配置文件。

    • 资源管理:如线程池、数据库连接池。

  • 需要控制资源访问的场景。

优缺点

优点:

  • 保证全局唯一实例,节省资源。

  • 提供全局访问点,方便使用。

  • 延迟加载(懒汉式),仅在需要时创建实例。

缺点:

  • 单例模式可能导致代码耦合度高,难以测试(无法mock单例对象)。

  • 多线程环境下需要加锁,可能会影响性能。

  • 不易扩展,若单例类需要子类化,设计会变得复杂。

C++实现

以下是一个线程安全的单例模式实现,结合了懒汉式和Meyers单例(利用C++11局部静态变量的线程安全特性)。

cpp

#include <iostream>
#include <mutex>

class Singleton {
public:
    // 获取单例实例的静态方法
    static Singleton& getInstance() {
        // C++11保证局部静态变量的初始化是线程安全的
        static Singleton instance;
        return instance;
    }

    // 示例方法
    void doSomething() {
        std::cout << "Singleton is doing something!" << std::endl;
    }

    // 删除拷贝构造和赋值操作,防止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    // 私有构造函数,防止外部实例化
    Singleton() {
        std::cout << "Singleton instance created!" << std::endl;
    }
};

int main() {
    // 获取单例实例并调用方法
    Singleton& instance1 = Singleton::getInstance();
    instance1.doSomething();

    // 再次获取,仍然是同一个实例
    Singleton& instance2 = Singleton::getInstance();
    instance2.doSomething();

    return 0;
}

代码解析

  1. 私有构造函数:Singleton()是私有的,防止外部通过new创建对象。

  2. 静态方法getInstance:返回局部静态变量instance,C++11保证其初始化是线程安全的。

  3. 禁用拷贝和赋值:通过删除拷贝构造函数和赋值操作符,确保单例无法被复制。

  4. 懒汉式加载:实例在第一次调用getInstance时创建,避免程序启动时的不必要开销。

输出结果

Singleton instance created!
Singleton is doing something!
Singleton is doing something!

总结

单例模式简单但实用,特别适合需要全局唯一资源的场景。在C++中,Meyers单例是推荐的实现方式,因为它简洁且线程安全。但需要注意单例模式可能带来的测试困难和耦合问题,在设计时应权衡利弊。


2. 简单工厂模式 (Simple Factory Pattern)

概述

简单工厂模式是一种创建型设计模式,通过一个工厂类根据参数创建不同类型的对象。它不属于GoF的23种设计模式,但因其简单易用,常被用作工厂方法模式的简化版。

设计思想

简单工厂模式的核心思想是:

  • 将对象的创建过程封装到一个工厂类中,客户端无需直接使用new创建对象。

  • 根据输入参数(如类型标识),工厂类返回对应的对象实例。

  • 解耦客户端与具体产品类,客户端只需与工厂交互。

适用场景

  • 需要根据条件创建不同类型的对象,但对象的创建逻辑较简单。

  • 客户端不关心对象的创建细节,只关心使用。

  • 产品种类较少,扩展需求不频繁。

优缺点

优点:

  • 封装对象创建过程,降低客户端与具体类的耦合。

  • 客户端代码简洁,只需调用工厂方法。

  • 便于集中管理对象的创建逻辑。

缺点:

  • 违反开闭原则:新增产品类需要修改工厂类代码。

  • 工厂类职责较重,可能成为瓶颈。

  • 不适合产品种类复杂或频繁扩展的场景。

C++实现

以下是一个简单工厂模式的实现,模拟一个形状工厂,创建圆形和矩形对象。

cpp

#include <iostream>
#include <memory>

// 抽象产品类
class Shape {
public:
    virtual void draw() = 0; // 纯虚函数
    virtual ~Shape() = default; // 虚析构函数
};

// 具体产品类:圆形
class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a Circle" << std::endl;
    }
};

// 具体产品类:矩形
class Rectangle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a Rectangle" << std::endl;
    }
};

// 简单工厂类
class ShapeFactory {
public:
    enum class ShapeType { CIRCLE, RECTANGLE };

    // 根据类型创建形状对象
    static std::unique_ptr<Shape> createShape(ShapeType type) {
        switch (type) {
            case ShapeType::CIRCLE:
                return std::make_unique<Circle>();
            case ShapeType::RECTANGLE:
                return std::make_unique<Rectangle>();
            default:
                return nullptr;
        }
    }
};

int main() {
    // 使用工厂创建对象
    auto circle = ShapeFactory::createShape(ShapeFactory::ShapeType::CIRCLE);
    auto rectangle = ShapeFactory::createShape(ShapeFactory::ShapeType::RECTANGLE);

    // 调用方法
    if (circle) circle->draw();
    if (rectangle) rectangle->draw();

    return 0;
}

代码解析

  1. 抽象产品类Shape:定义了draw接口,所有具体产品类(如Circle、Rectangle)都继承自它。

  2. 具体产品类:Circle和Rectangle实现了draw方法。

  3. 工厂类ShapeFactory:提供静态方法createShape,根据ShapeType参数返回对应的形状对象。

  4. 智能指针std::unique_ptr:管理对象生命周期,避免内存泄漏。

  5. 客户端代码:通过工厂创建对象并调用方法,无需直接new具体类。

输出结果

Drawing a Circle
Drawing a Rectangle

总结

简单工厂模式适合产品种类较少、创建逻辑简单的场景。它通过工厂类封装对象的创建,降低了客户端的耦合度。但当产品种类增多时,工厂类需要频繁修改,违背开闭原则。此时可考虑使用工厂方法模式或抽象工厂模式。


3. 观察者模式 (Observer Pattern)

概述

观察者模式是一种行为型设计模式,定义了对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并自动更新。它也被称为发布-订阅模式。

设计思想

观察者模式的核心思想是:

  • 将主题(Subject)和观察者(Observer)解耦,主题负责维护观察者列表并通知状态变化。

  • 观察者通过接口订阅主题,当主题状态变化时,调用观察者的更新方法。

  • 支持动态添加或移除观察者。

适用场景

  • 当一个对象的状态变化需要通知多个其他对象时。

  • 需要实现事件驱动系统,例如:

    • GUI框架中的事件监听。

    • 消息订阅系统(如新闻发布)。

    • 状态监控系统。

优缺点

优点:

  • 主题和观察者松耦合,各自可以独立扩展。

  • 支持动态添加或移除观察者。

  • 符合开闭原则,易于扩展新观察者。

缺点:

  • 通知所有观察者可能导致性能问题,尤其当观察者数量多时。

  • 观察者可能因依赖主题而导致内存泄漏(需注意移除)。

  • 通知顺序可能难以控制。

C++实现

以下是一个观察者模式的实现,模拟一个新闻发布系统,主题是新闻机构,观察者是订阅者。

cpp

#include <iostream>
#include <list>
#include <string>
#include <memory>

// 观察者接口
class Observer {
public:
    virtual void update(const std::string& news) = 0;
    virtual ~Observer() = default;
};

// 主题接口
class Subject {
public:
    virtual void attach(std::shared_ptr<Observer> observer) = 0;
    virtual void detach(std::shared_ptr<Observer> observer) = 0;
    virtual void notify() = 0;
    virtual ~Subject() = default;
};

// 具体主题:新闻机构
class NewsAgency : public Subject {
private:
    std::list<std::shared_ptr<Observer>> observers; // 观察者列表
    std::string latestNews; // 最新新闻

public:
    void attach(std::shared_ptr<Observer> observer) override {
        observers.push_back(observer);
    }

    void detach(std::shared_ptr<Observer> observer) override {
        observers.remove(observer);
    }

    void notify() override {
        for (auto& observer : observers) {
            observer->update(latestNews);
        }
    }

    // 发布新闻
    void publishNews(const std::string& news) {
        latestNews = news;
        notify();
    }
};

// 具体观察者:订阅者
class Subscriber : public Observer {
private:
    std::string name;

public:
    Subscriber(const std::string& name) : name(name) {}

    void update(const std::string& news) override {
        std::cout << name << " received news: " << news << std::endl;
    }
};

int main() {
    // 创建主题
    auto newsAgency = std::make_shared<NewsAgency>();

    // 创建观察者
    auto subscriber1 = std::make_shared<Subscriber>("Alice");
    auto subscriber2 = std::make_shared<Subscriber>("Bob");

    // 订阅
    newsAgency->attach(subscriber1);
    newsAgency->attach(subscriber2);

    // 发布新闻
    newsAgency->publishNews("Breaking News: New discovery!");

    // 取消订阅
    newsAgency->detach(subscriber2);

    // 再次发布新闻
    newsAgency->publishNews("Update: More details revealed!");

    return 0;
}

代码解析

  1. 观察者接口Observer:定义了update方法,所有具体观察者(如Subscriber)实现它。

  2. 主题接口Subject:定义了添加、移除观察者和通知的方法。

  3. 具体主题NewsAgency:维护观察者列表,发布新闻时通知所有观察者。

  4. 具体观察者Subscriber:接收并处理新闻更新。

  5. 智能指针std::shared_ptr:管理观察者对象,避免内存泄漏。

输出结果

Alice received news: Breaking News: New discovery!
Bob received news: Breaking News: New discovery!
Alice received news: Update: More details revealed!

总结

观察者模式非常适合事件驱动的场景,如GUI事件处理或消息订阅系统。它通过解耦主题和观察者,提高了系统的灵活性和可扩展性。但需要注意性能问题和内存管理,尤其在观察者数量较多时。


4. 状态模式 (State Pattern)

概述

状态模式是一种行为型设计模式,允许对象在内部状态变化时改变其行为,仿佛对象改变了类。状态模式常用于实现状态机,将状态相关的行为封装到独立的状态类中。

设计思想

状态模式的核心思想是:

  • 将每个状态封装为一个类,状态类实现具体的行为。

  • 上下文(Context)持有当前状态对象,并将行为委托给状态对象。

  • 通过切换状态对象,改变上下文的行为。

适用场景

  • 对象的行为依赖于其状态,且状态变化时行为显著不同。

  • 需要实现状态机,例如:

    • 订单处理系统(待支付、已支付、已发货等状态)。

    • TCP连接管理(建立、监听、关闭等状态)。

    • 游戏中角色状态(如站、跑、跳等状态)。

  • 希望避免使用大量if-else或switch来控制状态。

优缺点

优点:

  • 符合开闭原则,新增状态只需添加新状态类。

  • 每个状态的行为封装在独立类中,代码清晰,易于维护。

  • 避免状态转换逻辑分散,集中管理状态类。

缺点:

  • 状态较多的系统可能导致状态类数量激增。

  • 状态切换逻辑仍需在上下文或状态类中实现,可能复杂。

  • 增加了一些复杂性,可能不适合简单场景。

C++实现

以下是一个状态模式的实现,模拟一个订单系统,订单有“待支付”和“已支付”两种状态。

cpp

#include <iostream>
#include <memory>

// 前置声明
class Order; // 上下文

// 状态接口
class OrderStatus {
public:
    virtual void process(Order* order = nullptr) = 0;
    virtual ~OrderStatus() = default;
};

// 具体状态:待支付
class PendingPayment : public OrderStatus {
public:
    void process(Order* order) override {
        std::cout << "Processing order: Payment pending. Moving to paid status." << std::endl;
        order->setStatus(std::make_unique<Paid>());
    }
};

// 具体状态:已支付
class Paid : public OrderStatus {
public:
    void process(Order* order) override {
        std::cout << "Processing order: Already paid. No further action." << std::endl;
    }
};

// 上下文:订单
class Order {
private:
    std::unique_ptr<OrderStatus> currentStatus;

public:
    Order() : currentStatus(std::make_unique<PendingPayment>()) {}

    void setStatus(std::unique_ptr<OrderStatus> status) {
        currentStatus = std::move_ptr(status);
    }

    void process() {
        currentStatus->process(this);
    }
};

int main() {
    Order order;

    // 处理订单(初始状态:待支付)
    order.process();

    // 再次处理(已支付状态)
    order.process();

    return 0;
}

代码解析

  1. 状态接口OrderStatus:定义了process方法,所有具体状态类(如PendingPayment、Paid)实现它。

  2. 具体状态类:

    • PendingPayment:处理待支付状态,切换到已支付状态。

    • Paid:表示已支付状态,不执行进一步操作。

  3. 上下文类Order:持有当前状态对象,调用process时委托给状态对象。

  4. 智能指针:管理状态对象,避免内存泄漏。

输出结果

Processing order: Payment pending. Moving to paid status.
Processing order: Already paid. No further action.

总结

状态模式通过将状态行为封装到独立类中,提高了代码的可读性和灵活性,特别适合状态机场景。它避免了状态逻辑分散的问题,但当状态数量较多时,类数量会增加,需权衡复杂性与适用性。


5. 适配器模式 (Adapter Pattern)

概述

适配器模式是一种结构型设计模式,将一个类的接口转换为客户端期望的另一个接口,使原本不兼容的类能够一起工作。适配器模式常用于接口不兼容的场景,如集成第三方库或旧系统。

设计思想

适配器模式的核心思想是:

  • 通过适配器类(Adapter)包装不兼容的对象(Adaptee),将其接口转换为目标接口(Target)。

  • 客户端通过目标接口与适配器交互,适配器内部调用被适配对象的接口。

  • 分为类适配器(通过继承)和对象适配器(通过组合),对象适配器更常用。

适用场景

  • 需要复用现有类,但其接口与系统不兼容。

  • 集成第三方库或遗留代码时,需要统一接口。

  • 需要在不同系统之间进行接口转换,例如:

    • 将旧日志系统的API适配到新系统。

    • 统一不同支付接口。

优缺点

优点:

  • 提高代码复用性,隐藏不兼容接口的细节。

  • 符合开闭原则,客户端无需修改代码。

  • 对象适配器灵活,可动态替换被适配对象。

缺点:

  • 增加适配器类,增加了系统复杂性。

  • 可能需要多个适配器来处理不同的适配需求。

  • 性能可能略有开销(因多了一层调用)。

C++实现

以下是一个对象适配器模式的实现,模拟将一个旧日志系统的接口适配到新系统的日志接口。

cpp

#include <iostream>
#include <memory>

// 新系统的目标接口
class TargetLogger {
public:
    virtual void logMessage(const std::string& message) = 0;
    virtual ~TargetLogger() = default;
};

// 旧系统的日志类(被适配者)
class OldLogger {
public:
    void writeLog(const std::string& msg) {
        std::cout << "Old Logger: " << msg << std::endl;
    }
};

// 对象适配器
class LoggerAdapter : public TargetLogger {
private:
    std::unique_ptr<OldLogger> oldLogger;

public:
    LoggerAdapter() : oldLogger(std::make_unique<OldLogger>()) {}

    void logMessage(const std::string& message) override {
        oldLogger->writeLog(message);
    }
};

// 客户端代码
void useLogger(TargetLogger& logger, const std::string& message) {
    logger->logMessage(message);
}

int main() {
    // 创建适配器
    auto adapter = std::make_unique<LoggerAdapter>();

    // 使用适配器
    useLogger(*adapter, "Hello, Adapter Pattern!");

    return 0;
}

代码解析

  1. 目标接口TargetLogger:定义新系统期望的日志接口logMessage。

  2. 被适配者OldLogger:旧系统的接口,提供writeLog方法。

  3. 适配器LoggerAdapter:实现TargetLogger接口,内部持有OldLogger对象,将logMessage调用转换为writeLog。

  4. 客户端函数useLogger:通过目标接口使用日志功能,无需关心底层实现。

  5. 智能指针:管理对象,防止内存泄漏。

输出结果

Old Logger: Hello, Adapter Pattern!

总结

适配器模式是解决接口不兼容问题的有效方案,特别适合集成旧系统或统一接口的场景。对象适配器通过组合实现更灵活,推荐在实际开发中使用。需要注意适配器类可能增加的问题,设计时应尽量保持适配器简单。


综合总结

以上五种设计模式(单例模式、简单工厂模式、观察者模式、状态模式、适配器模式)是C++开发中常用的模式,每种模式都有其独特的设计思想和适用场景。以下是简要对比:

| 模式 | 类型 | 核心思想 | 典型场景 | 主要缺点 | |-----------------|------------|-----------------------------------|-----------------------------------| | 单例 | 创建型 | 保证全局唯一实例 | 配置管理、资源池 | 难测试,耦合度高 | | 简单工厂 | 创建型 | 封装对象创建 | 简单对象创建 | 违反开闭原则 | | 观察者 | 行为型 | 一对多状态通知 | 事件订阅、消息系统 | 性能问题,内存泄漏风险 | | 状态 | 行为型 | 状态切换改变行为 | 状态机、订单处理 | 状态类数量增加 | | 适配器 | 结构型 | 接口转换 | 旧系统集成、接口统一 | 增加复杂性 |

在实际开发中,建议根据具体场景选择合适的设计模式,并结合C++的智能指针、模板等特性优化实现。熟练掌握这些模式的设计思想和实现,能有效提高代码质量,同时也能在面试中相关问题时展现设计能力。

如果您有其他设计模式需要进一步讲解,或需要更深入的场景分析,请随时告知!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张工在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值