【C++】设计模式
面向对象是基础,设计模式是提高
何谓知识:未知,永无止境
学无止境,共勉
文章目录
一、设计模式的类型
-
创建型设计模式
- 用于构建对象,以便它们可以从实现系统中分离出来
-
结构型设计模式
- 用于在许多不同的对象之间形成大型对象结构
-
行为型设计模式
- 用于管理对象之间的算法、关系和职责
SOLID设计原则:
- 单一职责原则:一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分 (每个类只负责自己的事情,而不是变成万能)
- 开闭原则:软件实体应当对扩展开放,对修改关闭(扩展新类而不是修改旧类)
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中依然成立(继承父类而不是取改变父类)
- 接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上(各个类建立自己的专用接口,而不是建立万能接口–只暴露给它需要的接口,剩余的接口对于它是不透明的)
- 依赖倒转原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象(面向接口实现,而不是面向实现类–一个接口可以有多个类实现)
二、创建型设计模式
1、构造器模式
2、工厂模式
简易工厂模式
核心算法:一切从简
缺点:修改多了违反开闭原则
三个角色:
- 工厂
- 抽象产品
- 具体产品
// 抽象产品
class AbstractCar
{
public:
string engine;
virtual void run() = 0;
};
// 具体产品1
class VanCar : public AbstractCar
{
public:
VanCar() {this->engine = "三蹦子";}
void run() override
{
cout << engine << "50元一人" << endl;
}
};
// 具体产品2
class MiniCar : public AbstractCar
{
public:
MiniCar() {this->engine = "迷你宝马";}
void run() override
{
cout << engine << "10元一人" << endl;
}
};
// 简易工厂:只需要关注生产什么产品,不需要关注产品是如何生产的
// 缺点:如果有更多的产品,就需要在原程序上进行修改,违反了开闭原则,应该扩展出一个类进行扩展
class SimpleFactory
{
public:
AbstractCar* newCar(const string& type)
{
if(type == "三蹦子") return new VanCar();
else if(type == "迷你宝马") return new MiniCar();
// else if(type == "越野")
// else if(type == "跑车")
return nullptr;
}
};
int main()
{
// 创建一个工厂对象,用来生产产品
SimpleFactory factoty;
// 父类中装的是子类,调用子类的方法
AbstractCar* van = factoty.newCar("三蹦子");
AbstractCar* mini = factoty.newCar("迷你宝马");
van->run();
mini->run();
return 0;
}
工厂方法模式
单一产品族和不同产品等级
缺点:产品单一,比如只能生产汽车,不能生产手机、飞机等
四个角色:
- 抽象产品
- 具体产品
- 抽象工厂
- 具体工厂
// 抽象产品
class AbstractCar
{
public:
string engine;
virtual void run() = 0;
};
// 具体产品1
class VanCar : public AbstractCar
{
public:
VanCar() {this->engine = "三蹦子";}
void run() override
{
cout << engine << "50元一人" << endl;
}
};
// 具体产品2
class MiniCar : public AbstractCar
{
public:
MiniCar() {this->engine = "迷你宝马";}
void run() override
{
cout << engine << "10元一人" << endl;
}
};
// 对功能提高一个层次的做法:进行抽象
// 抽象工厂类
class ALLCarFactory
{
public:
virtual AbstractCar* createNewCar() = 0;
};
// 生产具体产品1的工厂:只用来生产产品1
// 具体工厂类
class VanCarFactory : public ALLCarFactory
{
public:
AbstractCar* createNewCar() override
{
return new VanCar();
}
};
// 生产具体产品2的工厂:只用来生产产品2
class MiniCarFactory : public ALLCarFactory
{
public:
AbstractCar* createNewCar() override
{
return new MiniCar();
}
};
// 对创建的产品进行定制化,如果还有其它更多的产品,只需要创建生产该产品的分工厂即可,符合开闭原则
int main()
{
// 创建一个抽象工厂对象:父类装的是子类产品1工厂对象
ALLCarFactory* factoty1 = new VanCarFactory();
// 使用具体工厂1创建产品1
AbstractCar* van = factoty1->createNewCar();
// 调用产品1的操作
van->run();
delete factoty1;
delete van;
// 创建一个抽象工厂对象: 父类装的是子类产品2工厂对象
ALLCarFactory* factoty2 = new MiniCarFactory();
// 使用具体工厂2创建产品2
AbstractCar* mini = factoty2->createNewCar();
mini->run();
delete factoty2;
delete mini;
return 0;
}
抽象工厂模式
多产品等级和多产品族
特点:需要什么就生产什么,不断定义抽象工厂
// 抽象产品:汽车族
class AbstractCar
{
public:
string engine;
virtual void run() = 0;
};
// 具体产品:汽车1
class VanCar : public AbstractCar
{
public:
VanCar() {this->engine = "三蹦子";}
void run() override
{
cout << engine << "50元一人" << endl;
}
};
// 具体产品:汽车2
class MiniCar : public AbstractCar
{
public:
MiniCar() {this->engine = "迷你宝马";}
void run() override
{
cout << engine << "10元一人" << endl;
}
};
// 抽象产品:手机族
class AbstractPhone
{
public:
int price;
virtual void run() = 0;
};
// 具体产品:手机1
class OldPhone : public AbstractPhone
{
public:
OldPhone() {this->price = 100;}
void run() override
{
cout << "旧手机诺基亚价格:" << this->price << endl;
}
};
// 具体产品:手机2
class NewPhone : public AbstractPhone
{
public:
NewPhone() {this->price = 8000;}
void run() override
{
cout << "新手机iphone价格:" << this->price << endl;
}
};
// 对功能提高一个层次的做法:进行抽象
// 抽象出一个淘宝总厂:总厂可以生产汽车,可以生产手机
class TaoBaoFactory
{
public:
virtual AbstractCar* createNewCar() = 0;
virtual AbstractPhone* createNewPhone() = 0;
};
// 抽象汽车总厂类:只用来生产汽车---这就是外包么..
class ALLCarFactory : public TaoBaoFactory
{
public:
virtual AbstractCar* createNewCar() = 0;
// 该外包工厂只用来生产汽车,不能用来生产手机
// 注意:在这一级实现了父类的抽象方法,就不需要将该方法暴露给子类了,完美
// 抽象汽车总厂的分工厂只需要专注与生产汽车即可
AbstractPhone* createNewPhone() override
{
return nullptr;
}
};
// 抽象手机总厂类:只用来生产手机
class ALLPhoneFactory : public TaoBaoFactory
{
public:
virtual AbstractPhone* createNewPhone() = 0;
// 该外包工厂只用来生产手机,不能用来生产汽车
AbstractCar* createNewCar() override
{
return nullptr;
}
};
// 抽象汽车总厂的分工厂:只用来生产单一汽车
class VanCarFactory : public ALLCarFactory
{
public:
AbstractCar* createNewCar() override
{
return new VanCar();
}
};
class MiniCarFactory : public ALLCarFactory
{
public:
AbstractCar* createNewCar() override
{
return new MiniCar();
}
};
// 抽象手机总厂的分工厂:只用来生产单一手机
class OldPhoneFactory : public ALLPhoneFactory
{
public:
AbstractPhone* createNewPhone() override
{
return new OldPhone();
}
};
class NewPhoneFactory : public ALLPhoneFactory
{
public:
AbstractPhone* createNewPhone() override
{
return new NewPhone();
}
};
int main()
{
// 一个总厂可以有不同的产品族
// 汽车产品族
// 创建一个汽车工厂对象:父类装的是抽象工厂1对象
// 注意:这里的汽车总厂也是个抽象类,不能创建对象,所以直接创建汽车分工厂的对象
TaoBaoFactory* factoty1 = new VanCarFactory();
// 该工厂只用来生产三蹦子
AbstractCar* van = factoty1->createNewCar();
van->run();
// 交给mini宝马汽车工厂进行生产
factoty1 = new MiniCarFactory();
AbstractCar* mini = factoty1->createNewCar();
mini->run();
// 手机产品族
// 总厂又想生产手机
factoty1 = new OldPhoneFactory();
AbstractPhone* old = factoty1->createNewPhone();
old->run();
// 生产iphone手机
factoty1 = new NewPhoneFactory();
AbstractPhone* newPho = factoty1->createNewPhone();
newPho->run();
return 0;
}
总结:
- 简单工厂模式(Simple Factory Pattern):
优点:
简单工厂模式对于客户端来说,隐藏了对象的创建细节,只需通过工厂类获取对象即可,降低了客户端的复杂性。
客户端只需关心产品的接口,而不需要关心具体的产品类。
缺点:
当需要新增产品时,需要修改工厂类的代码,违背了开闭原则。
工厂类集中了所有产品的创建逻辑,代码复杂度高,可维护性差。
- 工厂方法模式(Factory Method Pattern):
优点:
工厂方法模式符合开闭原则,当需要新增产品时,只需要创建相应的产品类和工厂类,而无需修改已有的代码。
客户端通过工厂接口来创建产品对象,客户端与具体产品类解耦。
缺点:
工厂方法模式会导致类的个数增加,每个具体产品都需要对应一个具体工厂类。
客户端需要知道具体的工厂类,增加了客户端的复杂度。
- 抽象工厂模式(Abstract Factory Pattern):
优点:
抽象工厂模式提供了一种统一的接口来创建一系列相关或相互依赖的产品对象,客户端无需关心具体的产品类和工厂类,只需通过接口来创建对象。
抽象工厂模式符合开闭原则,当需要新增产品族时,只需要创建相应的产品类和工厂类,而无需修改已有的代码。
缺点:
抽象工厂模式增加了系统的抽象性和复杂性,如果产品族很多,会导致工厂类的个数增加。
当需要新增产品等级结构时,需要修改抽象工厂接口及所有具体工厂类的代码。
需要根据具体的场景和需求来选择适合的工厂模式。简单工厂模式适用于产品较少且变化不频繁的情况,工厂方法模式适用于产品较多且需要扩展的情况,抽象工厂模式适用于需要创建一系列相关产品的情况。
3、原型模式
4、单例模式
概念:一个单一的类,负责创建自己的对象,同时确保系统中只有单个对象被创建。
关键点:
- 构造方法私有:它必须自行创建这个实例
- 对外提供实例化方法:它必须自行向整个系统提供这个实例
优化:考虑多线程状态下的单例模式
饿汉式:在类中先创建好一个静态对象,可设置为不可变,不需要加锁就能实现线程安全
class Person
{
/*
单例模式:懒汉式、饿汉式
单例模式的关键:确保只有一个实例被创建,并提供一种全局访问的方式
懒汉式:用的时候再创建对象
饿汉式:在类中先创建好一个静态对象,可设置为不可变
***
注意:饿汉模式在类加载的时候就创建了实例对象,因此不存在多线程竞争创建实例的情况,保证了线程安全性,在实现饿汉模式的时候,不需要使用锁;
在使用懒汉模式时,要注意处理多线程访问的线程安全问题,可以使用双重检查锁定等方式来保证线程安全性
***
为什么将获取单例实例的方法创建成静态方法?
答:设置为静态方法,静态方法可以在没有类实例的情况下直接通过类名调用,而不需要创建类的对象
在单例模式中,我们希望能够在程序的任何地方获取单例实例,并且该实例应该是唯一的。
如果将获取实例的方法设置为非静态方法,那么我们必须先创建一个类的对象,
然后通过该对象调用方法来获取实例。但这会导致一个矛盾的问题:我们希望获取实例,
但为了获取实例,我们又需要已经存在的实例。
*/
private:
// 构造方法私有化,防止外部直接创建实例
Person() {}
// 单例实例的指针
static Person* instance;
public:
// 创建对象的外部提供方法,获取单例实例的静态方法
// 设置为静态方法,静态方法可以在没有类实例的情况下直接通过类名调用,而不需要创建类的对象
// static Person& getPersonInstance()
// {
// // 懒汉式单例模式,在首次调用时创建实例
// if(instance == nullptr) instance = new Person();
// // 如果实例不为空,直接返回原来创建好的实例
// return *instance;
// }
static Person& getPersonInstance()
{
// 饿汉式单例模式,直接返回静态成员变量创建好的instance
return *instance;
}
// 删除拷贝构造函数和赋值运算符,防止通过拷贝创建新实例
Person(const Person&) = delete;
Person& operator=(const Person&) = delete;
// 其他成员函数
void showMessage() {
std::cout << "I'm the Person instance!" << std::endl;
}
};
// 静态成员变量需要在类外进行初始化
// 懒汉式需要初始化为空,在使用的时候再初始化实例
// Person* Person::instance = nullptr;
// 饿汉式需要先创建好一个对象
Person* Person::instance = new Person();
int main()
{
// 获取单例实例
Person& person = Person::getPersonInstance();
person.showMessage();
return 0;
}
懒汉式:实例只在需要时才会被创建,要注意处理多线程访问的线程安全问题,可以使用双重检查锁定等方式来保证线程安全性
Linux多线程环境下的单例模式实现,使用双重检查锁定(Double-Checked Locking)+ 互斥锁 确保线程安全
#include <iostream>
#include <pthread.h>
/*Linux环境下*/
class Person
{
private:
// 单例实例的指针
static Person* instance;
// 创建互斥量(锁)
static pthread_mutex_t mtx;
// 构造方法私有化,防止外部直接创建实例
Person()
{
pthread_mutex_init(&mtx, nullptr);
}
~Person()
{
pthread_mutex_destroy(&mtx);
}
public:
static Person& getPersonInstance()
{
// 懒汉式单例模式,用的时候再创建实例对象
// 使用双重检查锁定(Double-Checked Locking)+ 互斥锁 确保线程安全
if(instance == nullptr)// 第一次检查
{
pthread_mutex_lock(&mtx);// 加锁
if(instance == nullptr)// 第二次检查
{
instance = new Person();// 创建实例
}
pthread_mutex_unlock(&mtx);// 解锁
}
return *instance;
}
// 删除拷贝构造函数和赋值运算符,防止通过拷贝创建新实例
Person(const Person&) = delete;
Person& operator=(const Person&) = delete;
// 其他成员函数
void showMessage() {
std::cout << "I'm the Person instance!" << std::endl;
}
};
// 静态成员变量需要在类外进行初始化
// 懒汉式需要初始化为空,在使用的时候再初始化实例
Person* Person::instance = nullptr;
pthread_mutex_t Person::mtx = PTHREAD_MUTEX_INITIALIZER;
// 饿汉式需要先创建好一个对象
// Person* Person::instance = new Person();
// 线程执行的函数
void* threadFunc(void* arg) {
Person::getPersonInstance().showMessage();
return nullptr;
}
int main()
{
pthread_t t1, t2; // 定义线程变量
// 创建线程1
if (pthread_create(&t1, nullptr, threadFunc, nullptr) != 0) {
std::cout << "Failed to create thread 1." << std::endl;
exit(-1);
}
// 创建线程2
if (pthread_create(&t2, nullptr, threadFunc, nullptr) != 0) {
std::cout << "Failed to create thread 2." << std::endl;
exit(-1);
}
// 等待线程1结束
if (pthread_join(t1, nullptr) != 0) {
std::cout << "Failed to join thread 1." << std::endl;
exit(-1);
}
// 等待线程2结束
if (pthread_join(t2, nullptr) != 0) {
std::cout << "Failed to join thread 2." << std::endl;
exit(-1);
}
return 0;
}
使用了
std::mutex
和std::lock_guard
来替代了pthread_mutex_t
和pthread_create()
,以实现更现代化的线程安全机制。
#include <iostream>
#include <thread>
#include <mutex>
/*Linux环境下*/
class Person
{
private:
// 构造方法私有化,防止外部直接创建实例
Person() {}
// 单例实例的指针
static Person* instance;
// 声明锁
static std::mutex mtx;
public:
static Person& getPersonInstance()
{
// 懒汉式单例模式,用的时候再创建实例对象
// 使用双重检查锁定(Double-Checked Locking) + 锁 确保线程安全
if(instance == nullptr)
{
// 在构造时自动加锁,并在作用域结束时自动解锁
std::lock_guard<std::mutex> lock(mtx); // 加锁
if(instance == nullptr)
{
instance = new Person();// 创建实例
}
}
return *instance;
}
// 删除拷贝构造函数和赋值运算符,防止通过拷贝创建新实例
Person(const Person&) = delete;
Person& operator=(const Person&) = delete;
// 其他成员函数
void showMessage() {
std::cout << "I'm the Person instance!" << std::endl;
}
};
// 静态成员变量需要在类外进行初始化
// 饿汉式需要初始化为空,在使用的时候再初始化实例
Person* Person::instance = nullptr;
std::mutex Person::mtx;
// 懒汉式需要先创建好一个对象
// Person* Person::instance = new Person();
// 线程执行的函数
void threadFunc() {
Person::getPersonInstance().showMessage();
}
int main()
{
std::thread t1(threadFunc); // 创建线程1
std::thread t2(threadFunc); // 创建线程2
t1.join(); // 等待线程1结束
t2.join(); // 等待线程2结束
return 0;
}
优点:
- 更现代化的接口:C++11 的线程库提供了更现代化、更易用的接口,使用起来更加方便。
std::mutex
和std::lock_guard
是 C++ 标准库提供的线程安全机制,直接集成在语言和标准库中,无需依赖外部库。- RAII(资源获取即初始化)语法:
std::lock_guard
是一个轻量级的互斥锁封装,使用 RAII 语法,可以在作用域结束时自动释放锁资源,避免了手动加锁和解锁的繁琐操作。这样可以有效避免忘记解锁导致的死锁等问题。- 异常安全性:
std::lock_guard
在构造时就会加锁,并在析构时自动解锁,确保在发生异常时锁资源能够被正确释放,保证了代码的异常安全性。- 跨平台性:C++11 的线程库是 C++ 标准库的一部分,提供了跨平台的支持,可以在不同的操作系统上使用相同的接口编写可移植的多线程代码
三、结构型设计模式
1、装饰器模式
2、代理模式
四、行为型设计模式
1、职责链模式
2、观察者模式
qt中的信号槽机制类似于设计模式中的观察者模式,被观察者发出的信号(signal),观察者收到自己注册监听的signal,就通过槽(slot)关联的槽函数function实现动作操作
(观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新)