深入理解C++设计模式

C++ 设计模式是软件工程中应对复杂系统设计的方法,它总结了一系列在软件开发中反复出现的问题及其优雅的解决方案。掌握设计模式能显著提升代码的可读性、可维护性和可扩展性。

一.创建型模式

核心思想非常直观:将对象的创建过程与使用过程解耦(即将对象的创建与使用分离)

1.单例模式

单例模式 (Singleton):保证一个类在整个系统中只有一个实例,并提供一个全局访问点。常用于全局配置管理器、日志记录器、数据库连接池等。

核心要素

1.将构造函数设置为私有的

2.定义静态的对象,并提供一个共有的静态成员函数

3.将拷贝构造函数与赋值重载函数删除。

(1)饿汉式单例模式

饿汉式单例模式:特点:在程序进入 main 函数之前,静态对象 instance 就已经完成了构造,

优点:线程安全(无需加锁)

缺点:可能造成资源浪费(如果程序没有使用,这里也要进行构造)

cpp 复制代码
class Singleton
{
public:
	static Singleton* getinstance()
	{
		return &instance;
	}
private:
	static Singleton instance;
	Singleton()
	{

	}
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;

(2)懒汉式单例模式

汉式单例模式的特点是"延迟加载",只有当客户端第一次调用 getInstance() 方法请求实例时,它才会去创建这个唯一的对象。

优点:节省资源

缺点:需要自己实现线程安全(手动加锁)

线程安全的懒汉式单例模式:

锁+双重判断,(锁不易加在if语句外,这会导致单线程情况下,程序也需要加锁,解锁,锁加在if内部需要双重判断,否则依然存在线程安全问题)

cpp 复制代码
std::mutex mtx;
class Singleton
{
public:
	static Singleton* getinstance()
	{
		if (instance == nullptr)
		{
			lock_guard<mutex>lock(mtx);
			if (instance == nullptr)
			{
				instance = new Singleton();
			}
		}
		return instance;
	}
private:
	static Singleton*volatile instance;
	Singleton()
	{

	}
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};
Singleton* Singleton::instance=nullptr;

(3)Meyers Singleton单例实现方法

Meyers Singleton 被公认为是 C++11 及以后版本中最推荐、最优雅的单例实现方式.,

它结合了"懒汉式"的延迟加载优势(节省启动时间)和"饿汉式"的线程安全及自动管理优势,同时代码量最少,最不容易出错,函数静态局部变量的初始化,在汇编指令上已经自动添加线程互斥指令了。

cpp 复制代码
class Singleton
{
public:
	static Singleton* getinstance()
	{
		static Singleton instance;
		return &instance;
	}
private:
	
	Singleton()
	{

	}
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};

2.工厂模式

它的核心思想非常直观:将对象的创建和使用分离,简单来说,就是不要在业务代码里到处写 new 来实例化对象,而是把这些复杂的创建逻辑(比如初始化参数、选择具体子类等)统一交给一个专门的"工厂"去负责。调用者只需要告诉工厂"我要什么",工厂就会把造好的对象交给你,你完全不需要关心对象是怎么造出来的。

(1)简单工厂模式

它通过一个专门的工厂类,根据传入的参数(比如字符串或枚举),用 if-else 或 switch-case 来决定创建哪一种产品。

适用场景 :产品种类较少,且基本不会频繁变动。
优缺点:实现简单,调用者不需要关心创建细节。但它严重违反了开闭原则(对扩展开放,对修改关闭)。一旦要新增一种产品,就必须去修改工厂类里的判断逻辑,容易导致代码臃肿。

我们这里为Car类设计一个简单工厂,并提供A1.A2两个种产品类型。

cpp 复制代码
class Car//汽车基类
{
public:
	Car(string name) :_name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};
class A1 :public Car//汽车类型A1
{
public:
	A1(string name) :Car(name) {}
	void show() { cout << "A1" <<" " << _name << endl; }
};
class A2 :public Car//汽车类型A2
{
public:
	A2(string name) :Car(name) {}
	void show() { cout << "A2" <<" " << _name << endl; }
};
enum Cartype
{
	A1Type, A2Type
};
class SimpleFactory//简单工厂
{
public:
	Car* createCar(Cartype ct)
	{
		switch (ct)
		{
		case A1Type:
			return new A1("a1");
		case A2Type:
			return new A2("a2");
		default:
			cout << "传入参数不正确" << endl;
			return nullptr;
		}
	}
};

(2)工厂方法模式

为了解决简单工厂违反开闭原则的问题,工厂方法模式把"工厂"也抽象化了。它定义了一个创建对象的接口,但由子类(具体工厂)来决定实例化哪一个类。

适用场景 :产品类型会不断扩展,希望新增产品时完全不改动原有代码。
优缺点:完美符合开闭原则。新增产品时,只需要新增一个具体产品类和对应的具体工厂类。缺点是类的数量会成倍增加,系统复杂度变高。

我们可以将上面的简单工厂改成工厂方法:

cpp 复制代码
class Car//汽车基类
{
public:
	Car(string name) :_name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};
class A1 :public Car//汽车类型A1
{
public:
	A1(string name) :Car(name) {}
	void show() { cout << "A1" <<" " << _name << endl; }
};
class A2 :public Car//汽车类型A2
{
public:
	A2(string name) :Car(name) {}
	void show() { cout << "A2" <<" " << _name << endl; }
};

class Factory//工厂基类
{
public:
	virtual Car* createCar() = 0;
};
class A1Factory :public Factory//工厂A1
{
public:
	Car* createCar()
	{
		return new A1("a1");
	}
};
class A2Factory :public Factory//工厂A2
{
public:
	Car* createCar()
	{
		return new A2("a2");
	}
};

(3)抽象工厂模式

当产品不再是单一的一种,而是一个产品族时,就需要抽象工厂。它提供一个接口,用于创建一系列相关或相互依赖的对象。

适用场景 :需要创建一系列相互依赖的对象,并要保证它们之间的兼容性(比如跨平台的 UI 组件库,Windows 风格的一套,Mac 风格的一套)。
优缺点:保证了产品族内部的一致性。但扩展新的产品等级结构(比如给汽车再加一个"音响"配件)非常麻烦,需要修改所有工厂接口(即这里如果在抽象工厂中添加了一个方法,那么其他所有的子工厂都要重写这个方法,哪怕这个方法是它们不需要的)。

我们这里另外设计了一个Flight产品:

cpp 复制代码
class Car//汽车产品基类
{
public:
	Car(string name) :_name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};
class A1 :public Car//汽车类型A1
{
public:
	A1(string name) :Car(name) {}
	void show() { cout << "A1" <<" " << _name << endl; }
};
class A2 :public Car//汽车类型A2
{
public:
	A2(string name) :Car(name) {}
	void show() { cout << "A2" <<" " << _name << endl; }
};

class Flight//飞机基类
{
public:
	Flight(string name) :_name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};
class F1 :public Flight//飞机类型F1
{
public:
	F1(string name) :Flight(name) {}
	void show() { cout << "F1" << " " << _name << endl; }
};
class F2 :public Flight//飞机类型F2
{
public:
	F2(string name) :Flight(name) {}
	void show() { cout << "F2" << " " << _name << endl; }
};

class AbstractFactory//抽象工厂基类
{
public:
	virtual Car* createCar() = 0;
	virtual Flight* createFlight() = 0;
};
class AF1Factory :public AbstractFactory//工厂AF1,负责生产A1,F1
{
public:
	Car* createCar()
	{
		return new A1("a1");
	}
	Flight* createFlight()
	{
		return new F1("f1");
	}
};
class AF2Factory :public AbstractFactory//工厂AF2,负责生产A2,F2
{
public:
	Car* createCar()
	{
		return new A2("a2");
	}
	Flight* createFlight()
	{
		return new F2("f2");
	}
};

int main()
{
	AbstractFactory* factoryA1 = new AF1Factory();
	AbstractFactory* factoryA2 = new AF2Factory();
	factoryA1->createCar()->show();
	factoryA2->createCar()->show();
	factoryA1->createFlight()->show();
	factoryA2->createFlight()->show();


}

二.结构型模式

结构型模式主要解决 的是"如何将类或对象组合成更大的结构"。

如果说创建型模式(如工厂)关注的是"怎么 new 对象",那么结构型模式关注的就是"怎么把对象拼起来用"。它们就像建筑里的"连接件",负责把独立的类组装成灵活且稳固的系统。

1.代理模式

核心作用:为对象提供一个替身(代理),以控制对这个对象的访问。代理通常负责对象的创建、销毁或访问前后的预处理。

使用场景:远程代理,虚拟代理,保护代理

优缺点:职责分离,控制访问,但引入了额外的中间层(间接性),可能会带来轻微的性能开销,且增加了类的数量。

我们这里设计一个视频网站观看权限的例子,我们这里对用户不同的身份进行了代理,来负责对象的访问权限问题:

cpp 复制代码
class VideoSite//视频网站基类
{
public:
	virtual void freemovie() = 0;
	virtual void VIPmovie() = 0;
};

class VideoSiteBackend :public VideoSite//委托类
{
public:
	void freemovie()
	{
		cout << "see freemovie" << endl;
	}
	void VIPmovie()
	{
		cout << "see VIPmovie" << endl;
	}

};

class Ordinarymember :public VideoSite//普通用户的代理
{
public:
	Ordinarymember() { p = new VideoSiteBackend(); }
	~Ordinarymember() { delete p; }
	void freemovie()
	{
		p->freemovie();
	}
	void VIPmovie()
	{
		cout << "Insuffcient permissions at present " << endl;
	}
private:
	VideoSite* p;
};

class VIPmember :public VideoSite//VIP用户的代理
{
public:
	VIPmember() { p = new VideoSiteBackend(); }
	~VIPmember() { delete p; }
	void freemovie()
	{
		p->freemovie();
	}
	void VIPmovie()
	{
		p->VIPmovie();
	}
private:
	VideoSite* p;
};

int main()
{
	unique_ptr< VideoSite>p1(new Ordinarymember());
	unique_ptr< VideoSite>p2(new VIPmember());
	p1->freemovie();
	p1->VIPmovie();
	p2->freemovie();
	p2->VIPmovie();

}

2.装饰器模式

核心作用 :在不改变原有对象结构的前提下,动态地为对象添加新的功能或职责,是继承的灵活替代方案

使用场景:要给对象动态增加功能,且这些功能可以随意组合,不想通过大量继承来扩展功能(避免"子类爆炸")

优点

比继承更灵活:可以在运行时自由叠加、撤销职责。

符合开闭原则:新增功能只需增加新的装饰器类,无需修改原类。
缺点

多层装饰会导致产生很多小对象,增加系统的复杂性,且调试排查问题(堆栈跟踪)会比较麻烦。

例如,我们这里想要给我们的两种汽车添加两种功能,这两种功能对于两种汽车是相同的,那么如果使用继承的话,我们就需要为两种汽车各自创建一个子类,但是我们这里使用装饰器,可以减少子类,降低继承。

cpp 复制代码
class Car//汽车基类
{
public:
	Car(){}
	Car(string name) :_name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};
class A1 :public Car//汽车类型A1
{
public:
	A1(string name) :Car(name) {}
	void show() { cout << "A1" << " " << _name << endl; }
};
class A2 :public Car//汽车类型A2
{
public:
	A2(string name) :Car(name) {}
	void show() { cout << "A2" << " " << _name << endl; }
};
class Decorate1:public Car//装饰器1
{
public:
	Decorate1(Car* p):_p(p){ }
public:
	void show()
	{
		_p->show();
		cout << "功能1" << endl;
	}
private:
	Car* _p;
};
class Decorate2 :public Car//装饰器2
{
public:
	Decorate2(Car* p) :_p(p) {}
public:
	void show()
	{
		_p->show();
		cout << "功能2" << endl;
	}
private:
	Car* _p;
};

int main()
{
	Car* p1 = new Decorate1(new A1("a1"));
	p1 = new Decorate2(p1);
	p1->show();//递归调用
	Car* p2 = new Decorate1(new A2("a2"));
	p2 = new Decorate2(p2);
	p2->show();
}

3.适配器模式

核心作用:解决接口不兼容的问题,充当"转接头",让原本因为接口不匹配而无法一起工作的类能够协同工作。

使用场景:需要复用现有的类,但它的接口与当前系统的需求不兼容,统一多个风格迥异的接口

优点

提高兼容性:让原本不相关的类可以一起工作。

遵循开闭原则:通过增加适配器来扩展,无需修改原有代码。
缺点

过多使用适配器会让系统变得杂乱,增加代码的复杂度和理解难度。

我们这里实现一个接口转化的适配器:

cpp 复制代码
class VGA
{
public:
	virtual void show()=0;
};

class TV :public VGA//TV需要VGA接口
{
	void show()
	{
		cout << "VGA connection" << endl;
	}
};

class HDMI
{
public:
	virtual void show() = 0;
};

class Computer
{
public:
	void show(HDMI* p)//电脑只支持HDMI接口
	{
		p->show();
	}
};

class HDMItoVGAAdapt:public HDMI//将HDMI接口转化为VGA接口
{public:
	ComputerAdapt(TV* p):_p(p){}
	void show()
	{
		_p->show();
	}
private:
	VGA* _p;
};

int main()
{
	Computer computer;
	computer.show(new HDMItoVGAAdapt(new TV()));//电脑通过接口投影到TV

}

三.行为型模式

1.观察者模式(发布-订阅模式)

是软件开发中最经典、最常用的一种行为型设计模式。

核心作用:定义对象间的一种一对多的依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并被自动更新。

使用场景:凡是一个动作需要触发多个后续流程,或者一个对象的状态变化需要通知其他多个对象的场景,都可以考虑使用观察者模式(如:图形用户界面(GUI)事件处理,实时数据更新,跨系统的消息交换)

优点:高度解耦(核心优势),易于扩展(符合开闭原则)

我们这里设计一个主题,两个观察者,观察者1对消息1感兴趣,观察者2对消息2感兴趣,当主题的消息1更新,那么感兴趣的所有观察者(这里只有观察者1)都会收到消息,观察者2同理。

cpp 复制代码
class Observer
{
public:
	virtual void handle(int msgid) = 0;
};
class Observer1 :public Observer
{
	void handle(int msgid)
	{
		switch (msgid)
		{
		case 1:
			cout << "Observer1 recv 1 msg " << endl;
			break;
		}
	}
	
};
class Observer2 :public Observer
{
	void handle(int msgid)
	{
		switch (msgid)
		{
		case 2:
			cout << "Observer2 recv 2 msg " << endl;
			break;
		}
	}
};
class Subject
{
public:
	void addObserver(Observer* obser, int msgid)
	{
		mp[msgid].push_back(obser);
	}
	void dispatch(int msgid)
	{
		auto it = mp.find(msgid);
		if (it != mp.end())
		{
			for (Observer* pObser : it->second)
			{
				pObser->handle(msgid);
			}
		}
	}
private:
	unordered_map<int, list<Observer*>> mp;
};
int main()
{
	Subject subject;
	Observer* p1 = new Observer1();
	Observer* p2 = new Observer2();
	subject.addObserver(p1, 1);
	subject.addObserver(p2, 2);
	subject.dispatch(1);
	subject.dispatch(2);
}
相关推荐
昵称小白2 小时前
C++ 刷题语法速查
c++·算法
Qt程序员2 小时前
【无标题】
linux·c++·消息队列·共享内存·c/c++·管道·信号量
十五年专注C++开发2 小时前
Qt程序设计涉及到的开发软件
开发语言·c++·qt
AI大法师4 小时前
从门头到社媒预热图,快闪项目如何统一视觉输出
大数据·人工智能·设计模式
邪修king4 小时前
C++ typename & auto 彻底讲透:核心作用、推导规则、避坑指南
开发语言·c++
姆路4 小时前
Qt尺寸策略
c++·qt
khalil10204 小时前
代码随想录算法训练营Day-41动态规划08 | 121. 买卖股票的最佳时机、122.买卖股票的最佳时机II、123.买卖股票的最佳时机III
数据结构·c++·算法·leetcode·动态规划
Vect__4 小时前
C++无痛转go第一天,从hello world到切片
开发语言·c++·golang
Pkmer5 小时前
类的封装性: 让门面设计模式来打开这扇门
后端·设计模式