以下为五种常见的设计模式(单例模式、简单工厂模式、观察者模式、状态模式、适配器模式)的详细解释和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;
}
代码解析
-
私有构造函数:Singleton()是私有的,防止外部通过new创建对象。
-
静态方法getInstance:返回局部静态变量instance,C++11保证其初始化是线程安全的。
-
禁用拷贝和赋值:通过删除拷贝构造函数和赋值操作符,确保单例无法被复制。
-
懒汉式加载:实例在第一次调用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;
}
代码解析
-
抽象产品类Shape:定义了draw接口,所有具体产品类(如Circle、Rectangle)都继承自它。
-
具体产品类:Circle和Rectangle实现了draw方法。
-
工厂类ShapeFactory:提供静态方法createShape,根据ShapeType参数返回对应的形状对象。
-
智能指针std::unique_ptr:管理对象生命周期,避免内存泄漏。
-
客户端代码:通过工厂创建对象并调用方法,无需直接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;
}
代码解析
-
观察者接口Observer:定义了update方法,所有具体观察者(如Subscriber)实现它。
-
主题接口Subject:定义了添加、移除观察者和通知的方法。
-
具体主题NewsAgency:维护观察者列表,发布新闻时通知所有观察者。
-
具体观察者Subscriber:接收并处理新闻更新。
-
智能指针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;
}
代码解析
-
状态接口OrderStatus:定义了process方法,所有具体状态类(如PendingPayment、Paid)实现它。
-
具体状态类:
-
PendingPayment:处理待支付状态,切换到已支付状态。
-
Paid:表示已支付状态,不执行进一步操作。
-
-
上下文类Order:持有当前状态对象,调用process时委托给状态对象。
-
智能指针:管理状态对象,避免内存泄漏。
输出结果
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;
}
代码解析
-
目标接口TargetLogger:定义新系统期望的日志接口logMessage。
-
被适配者OldLogger:旧系统的接口,提供writeLog方法。
-
适配器LoggerAdapter:实现TargetLogger接口,内部持有OldLogger对象,将logMessage调用转换为writeLog。
-
客户端函数useLogger:通过目标接口使用日志功能,无需关心底层实现。
-
智能指针:管理对象,防止内存泄漏。
输出结果
Old Logger: Hello, Adapter Pattern!
总结
适配器模式是解决接口不兼容问题的有效方案,特别适合集成旧系统或统一接口的场景。对象适配器通过组合实现更灵活,推荐在实际开发中使用。需要注意适配器类可能增加的问题,设计时应尽量保持适配器简单。
综合总结
以上五种设计模式(单例模式、简单工厂模式、观察者模式、状态模式、适配器模式)是C++开发中常用的模式,每种模式都有其独特的设计思想和适用场景。以下是简要对比:
| 模式 | 类型 | 核心思想 | 典型场景 | 主要缺点 | |-----------------|------------|-----------------------------------|-----------------------------------| | 单例 | 创建型 | 保证全局唯一实例 | 配置管理、资源池 | 难测试,耦合度高 | | 简单工厂 | 创建型 | 封装对象创建 | 简单对象创建 | 违反开闭原则 | | 观察者 | 行为型 | 一对多状态通知 | 事件订阅、消息系统 | 性能问题,内存泄漏风险 | | 状态 | 行为型 | 状态切换改变行为 | 状态机、订单处理 | 状态类数量增加 | | 适配器 | 结构型 | 接口转换 | 旧系统集成、接口统一 | 增加复杂性 |
在实际开发中,建议根据具体场景选择合适的设计模式,并结合C++的智能指针、模板等特性优化实现。熟练掌握这些模式的设计思想和实现,能有效提高代码质量,同时也能在面试中相关问题时展现设计能力。
如果您有其他设计模式需要进一步讲解,或需要更深入的场景分析,请随时告知!
的详细解释和C++示例代码&spm=1001.2101.3001.5002&articleId=148613346&d=1&t=3&u=61b7fc7db0124e6cb8d7e78816e8cfa3)
392

被折叠的 条评论
为什么被折叠?



