前言
本篇的设计模式主要是:单例模式、工厂方法模式、策略模式、观察者模式、适配器模式。
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
特点
- 唯一实例:类自己控制实例创建,保证全局只有一个对象。
- 私有构造函数:构造函数设为
private,不适合被继承,且外部不能随意new对象。 - 禁止拷贝和赋值:删除拷贝构造、拷贝赋值、移动构造、移动赋值,防止通过复制产生第二个实例。
- 提供全局访问点:通过静态方法如**
instance()或getInstance()**返回唯一实例的引用或指针。 - 内部只创建一次对象:通常用**
static局部变量**实现。
样例及说明
单例模式因为构造函数设为private ,不适合被继承,所以它的模式中很少有virtual虚函数(C++语法上是可以有,因为不适合被继承,所以很少有虚函数)。static 局部变量 使得它的生命周期从第一次执行到该声明语句时 进行构造和初始化,而结束则是析构发生在 main() 函数返回之后。
static 局部变量的特性在【C++开发】从裸指针到智能指针-CSDN博客中有介绍。
单例模式类实现
//configmanager.h
#ifndef CONFIGMANAGER_H
#define CONFIGMANAGER_H
#include <QObject>
#include <QSettings>
/******************************************************
* 配置管理类(单例模式)
* 用于统一管理应用程序的配置信息,例如用户名、窗口大小等。
* 使用 QSettings 进行本地持久化存储。
* 该类禁止拷贝和赋值,确保全局只有一个实例。
******************************************************/
class ConfigManager : public QObject
{
Q_OBJECT
public: //外部可调用的类成员函数
//获取 ConfigManager 全局唯一实例
//采用局部静态变量实现线程安全
static ConfigManager &instance();
QString userName() const; //返回配置文件中的用户名称
void setUserName(const QString &name); //修改配置文件中的用户名称
//保存当前配置到持久化存储
//通常在程序退出或配置变更时调用
void save();
private:
//私有构造函数,防止外部通过 new 创建实例,确保单例特性
explicit ConfigManager(QObject *parent = nullptr);
//禁用拷贝构造函数
ConfigManager(const ConfigManager &) = delete;
//禁用赋值运算符
ConfigManager &operator=(const ConfigManager &) = delete;
//用于读写配置文件
QSettings m_settings;
};
#endif // CONFIGMANAGER_H
// configmanager.cpp
#include "configmanager.h"
ConfigManager::ConfigManager(QObject *parent)
: QObject(parent),
m_settings("MyCompany", "SingletonDemo")
{
}
ConfigManager &ConfigManager::instance()
{
static ConfigManager manager;
return manager;
}
QString ConfigManager::userName() const
{
return m_settings.value("user/name", "Guest").toString();
}
void ConfigManager::setUserName(const QString &name)
{
m_settings.setValue("user/name", name);
}
void ConfigManager::save()
{
m_settings.sync();
}
调用方式
在单例模式类中明确定义了静态成员函数static 单例模式类 &instance(); ,只有这样它才不属于某个对象(不允许new),而是属于类本身,此时统一以此方式调用这个类的成员函数:单例模式类::instance().xxx ,如下
#include "configmanager.h"
void main(){
QString userName=ConfigManager::instance().userName();
QString newName="testname";
ConfigManager::instance().setUserName(newName);
ConfigManager::instance().save();
}
工厂方法模式
定义一个创建对象的接口,让子类决定实例化哪一个类,使创建过程延迟到子类进行。
特点
- 创建和使用分离:业务代码不直接
new具体类,而是调用工厂方法。 - 面向抽象编程:返回基类指针、引用或智能指针,上层不依赖具体实现。
- 统一创建入口:所有对象都通过工厂创建,便于集中管理初始化逻辑。
- 隐藏细节:调用者不需要知道对象内部怎么构造、依赖哪些资源。
样例及说明
基类提供纯虚函数,子类重写虚函数,并且工厂子类创建具体对象,返回具体对象的智能指针。
工厂方法类实现
产品和工厂基类
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() = 0;
};
class ShapeFactory {
public:
virtual ~ShapeFactory() = default;
virtual std::unique_ptr<Shape> create() = 0;
};
产品和工厂子类
创建过程延迟到子类进行。
//------------------------具体产品-----------------
class Circle : public Shape {
public:
void draw() override {
std::cout << "Draw Circle" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Draw Rectangle" << std::endl;
}
};
//------------------------具体工厂-----------------
class CircleFactory : public ShapeFactory {
public:
std::unique_ptr<Shape> create() override {
return std::make_unique<Circle>();//延迟到具体工厂创建具体对象
}
};
class RectangleFactory : public ShapeFactory {
public:
std::unique_ptr<Shape> create() override {
return std::make_unique<Rectangle>();//延迟到具体工厂创建具体对象
}
};
调用方法
先创建工厂之类,工厂之类在调用创建产品的函数,返回的是具体产品的智慧指针。
std::unique_ptr<ShapeFactory> factory = std::make_unique<CircleFactory>();
auto shape = factory->create();
shape->draw();
策略模式
把"做什么"和"怎么做"拆开,用组合替代继承,让算法可以在运行时互换。
策略模式通常有三个角色:
- Strategy(策略接口):虚拟函数接口类,定义了所有支持算法的公共接口。
- ConcreteStrategy(具体策略):继承接口的子类,实现了具体的业务逻辑。
- Context(上下文):主框架,持有接口的指针,负责调用算法或行为。
关键点在于:Context 不关心具体是哪个策略,只认接口。
QT的低级插件QPlugin就是以策略模式构建,我之前分享的博文里面也专门介绍有:
观察者模式
观察者模式本质是一对多的通知机制:一个对象状态变化时,自动通知所有关心它的对象,而不需要知道它们具体是谁。
QT的信号和槽机制和观察者模式契合度非常高,并且也支持一个信号发送,多个槽接收。
另外QT的事件系统也是这个机制。除了几个常用事件,需要捕获指定事件时,需要重写event()函数,根据事件类型触发相关的操作。
特点
| 角色 | 职责 |
|---|---|
| Subject(被观察者) | 维护观察者列表,提供注册/注销/通知接口 |
| Observer(观察者) | 定义统一的 update() 接口,接收通知 |
| ConcreteSubject | 具体被观察者,状态变化时调用 notify() |
| ConcreteObserver | 具体观察者,实现 update() 响应逻辑 |
样例及说明
观察者基类提供纯虚函数;
观察者子类重写虚函数;
被观察者通过vector累加/减观察者,通过notify(通知)遍历vector逐个调用观察者子类重写的触发函数。
观察者模式类实现
观察者基类
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string &msg) = 0;
};
被观察者
attach/detach/notify 都可能被多线程并发调用,必须加锁。
attach(注册)、detach(注销)被直接对容器std::vector<std::shared_ptr<Observer>>进行增/删操作。
notify(通知)遍历vector逐个调用观察者子类重写的触发函数。
class Subject {
public:
void attach(std::shared_ptr<Observer> obs) {
std::lock_guard<std::mutex> lock(mtx);
observers.push_back(obs);
}
void detach(std::shared_ptr<Observer> obs) {
std::lock_guard<std::mutex> lock(mtx);
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void notify(const std::string &msg) {
std::lock_guard<std::mutex> lock(mtx);
for (auto &obs : observers) {
obs->update(msg);
}
}
private:
std::vector<std::shared_ptr<Observer>> observers;
std::mutex mtx;
};
观察者子类
重写的触发函数update。
class LogObserver : public Observer {
public:
void update(const std::string &msg) override {
std::cout << "[日志] " << msg << std::endl;
}
};
class UIObserver : public Observer {
public:
void update(const std::string &msg) override {
std::cout << "[界面] 更新显示: " << msg << std::endl;
}
};
调用方法
int main() {
Subject subject;
auto log = std::make_shared<LogObserver>();
auto ui = std::make_shared<UIObserver>();
subject.attach(log);
subject.attach(ui);
subject.notify("录像开始"); // 两个观察者都会收到
subject.notify("录像结束");
subject.detach(log); // 移除日志观察者
subject.notify("设备离线"); // 只有界面收到
}
适配器模式
在不修改原有类的前提下,把一个不兼容的接口转换成客户端期望的接口。适配器模式在数据中间件、API网关、流媒体、数据接入层等场景比较常见。
特点
中间件通常要连接很多不同系统:不同协议、不同数据格式、不同厂商 SDK、不同版本 API。适配器模式正好解决这种问题:
- 把外部不兼容接口转换成内部统一接口;
- 屏蔽第三方 SDK、协议、数据格式差异;
- 新增数据源时,尽量不动核心业务代码;
- 第三方接口升级、替换时,只改适配器,不改主流程。
样例及说明
适配器模式类实现
原有接口类
class ILogger {
public:
virtual ~ILogger() = default;
virtual void log(const std::string &msg) = 0;
virtual void error(const std::string &msg) = 0;
};
第三接口类
class FastLogger {
public:
void writeLog(const char *msg, int level);
};
接口适配器类
在构造函数中转移右值,重写虚函数
// 把 FastLogger 适配成 ILogger
class LoggerAdapter : public ILogger {//继承原有接口
public:
explicit LoggerAdapter(std::unique_ptr<FastLogger> logger)//转移右值
: logger_(std::move(logger)) {}
void log(const std::string &msg) override {//重写虚函数
logger_->writeLog(msg.c_str(), 0);
}
void error(const std::string &msg) override {//重写虚函数
logger_->writeLog(msg.c_str(), 1);
}
private:
std::unique_ptr<FastLogger> logger_;
};
调用方法
// 业务代码:只认 ILogger,完全不知道 FastLogger 的存在
class OrderService {
public:
explicit OrderService(ILogger &logger) : logger_(logger) {}
void createOrder() {
logger_.log("creating order...");
logger_.error("create order failed: insufficient stock");
}
private:
ILogger &logger_;
};
int main() {
// 把第三方对象塞进适配器
auto logger = std::make_unique<LoggerAdapter>(std::make_unique<FastLogger>());
//方法一:自己直接调用
logger->log("app started");//直接调
logger->error("something went wrong");//直接调
//方法二:把适配器"借"给业务类
OrderService service(*logger);
service.createOrder();
return 0;
}