常见设计模式简述
工厂方法
工厂模式:封装对象的创建
简单工厂
class Car{ public: Car(string name = "车") :name(name) {} virtual void show() { cout << name << endl; } private: string name; }; class Bmw : public Car { public: Bmw(string name = "宝马"):name(name) {} void show() { cout << name << endl; } private: string name; }; class Audi : public Car { public: Audi(string name = "奥迪"):name(name) {} void show() { cout << name << endl; } private: string name; }; typedef enum CarType { BMW, AUDI }CarType; // 简单工厂 class SimpleFactory { public: Car* createCar(CarType ct) { switch (ct) { case BMW: return new Bmw(); break; case AUDI: return new Audi(); break; default: cerr << "传入工厂的参数不正确" << endl; break; } } };
简单工厂使得汽车对象可以通过调用工厂对象的createCar方法产生,而不用去关心具体的汽车对象构造方法,但是此时存在一个问题——如果新增一种汽车对象的创建,就得在枚举类型CarType中新增一个类型,以及在工厂类中新增一个case,这显然是不符合软件开发的“开—闭”原则的(对扩展开放,对修改关闭)。
工厂方法(工厂模式)
class Factory { public: virtual Car* createCar(string name) = 0; }; class BMWFactory : public Factory { public: Car* createCar(string name) { return new Bmw(name); } }; class AudiFactory : public Factory { public: Car* createCar(string name) { return new Audi(name); } }; int main() { unique_ptr<BMWFactory> bmwFac(new BMWFactory()); unique_ptr<AudiFactory> audiFac(new AudiFactory()); unique_ptr<Car> bmw(bmwFac->createCar("X6")); unique_ptr<Car> audi(audiFac->createCar("A8")); bmw->show(); audi->show(); return 0; return 0; }
工厂方法,将工厂基类作为抽象类,由每个汽车工厂去实现各自品牌汽车的创建方法。这种情况下,如果新增一种汽车对象的创建,那么就新创建一个对应汽车的工厂类,继承工厂基类,实现createCar方法即可。也就是说,不需要对已经创建的类进行修改,而是新增一个汽车工厂类继承基类来实现功能,这样就很好的做到了“开—闭”原则。
抽象工厂
工厂模式中,每个工厂生产一种产品。抽象工厂则是在工厂模式的基础上,将有关联的产品封装到一个工厂类中,例如汽车不仅有车身,还车灯等产品,那么就在抽象工厂基类中提供车身和车灯两个产品创建方法,然后在每个汽车工厂类中实现所有继承自抽象工厂基类的方法。也就是说,一个品牌汽车工厂生产所有有关这个品牌的汽车产品。
// 产品一:车 class Car{ public: virtual void show() = 0; }; class Bmw : public Car { public: Bmw(string name = "宝马"):name(name) {} void show() { cout << name << endl; } private: string name; }; class Audi : public Car { public: Audi(string name = "奥迪"):name(name) {} void show() { cout << name << endl; } private: string name; }; // 产品二:灯 class Light { public: virtual void show() = 0; }; class BmwLight : public Light { public: void show() { cout << "BMW light" << endl; } }; class AudiLight : public Light { public: void show() { cout << "Audi light" << endl; } };
// 抽象工厂(对一组由关联的产品簇提供产品对象的统一创建) class AbstractFactory { public: virtual Car* createCar(string name) = 0; virtual Light* createLight() = 0; }; class BMWFactory : public AbstractFactory { public: Car* createCar(string name) { return new Bmw(name); } Light* createLight() { return new BmwLight(); } }; class AudiFactory : public AbstractFactory { public: Car* createCar(string name) { return new Audi(name); } Light* createLight() { return new AudiLight(); } }; int main() { unique_ptr<AbstractFactory> bmwFac(new BMWFactory()); unique_ptr<AbstractFactory> audiFac(new AudiFactory()); unique_ptr<Car> bmw(bmwFac->createCar("X6")); unique_ptr<Car> audi(audiFac->createCar("A8")); unique_ptr<Light> bmwLight(bmwFac->createLight()); unique_ptr<Light> audiLight(audiFac->createLight()); bmw->show(); audi->show(); bmwLight->show(); audiLight->show(); return 0; }
总结
简单工厂:把对象的创建封装在一个接口函数里面,通过传入不同的标识,返回不同的对象
好处:客户不用自己负责创建对象,不用了解对象创建的详细过程
缺点:提供创建对象实例的接口函数不闭合,不能对修改关闭
工厂方法:Factory基类提供一个创建产品的纯虚函数,定义派生类(具体的产品工厂)实现创建产品的函数,负责对应产品对象的创建。做到不同的产品,在不同的工厂里面创建。并且能够对现有工厂和产品的修改关闭。
缺点:很多产品有关联关系,属于一个产品簇,不应该放在不同的工厂里面创建,这样:一是不符合实际的产品创建逻辑,二是工厂类太多,不好维护
抽象工厂:把有关联关系的,属于一个产品簇的所有产品的创建函数,放到一个抽象工厂里面。同样的,AbstractFactory提供这些产品创建的纯虚函数,由派生类(具体产品的工厂)负责实现这些产品创建的纯虚函数。
代理模式(Proxy Pattern)
作用:通过代理来访问实际对象,以此来控制访问
代理模式的主要角色包括:
- 主题(Subject) :抽象基类,定义了业务的接口,并且可以通过多态的方式作为接收真实主题对象和代理对象的变量的数据类型。
- 真实主题(Real Subject) :实现了主题中的具体业务逻辑。
- 代理(Proxy) :保存一个真实主题的引用使得代理可以访问真实主题,代理对象是客户对象和真实主题对象之间的中介,不同的客户走不同的代理,能够访问到的业务不同。
- 客户(Client) :与代理对象进行交互的代码
代理模式的应用场景包括:
- 当需要为一个对象的访问提供额外的安全控制时。
- 当需要延迟对象的创建以节约资源时。
- 当需要对对象的访问进行监控或记录日志时。
- 当需要控制对不同对象访问的权限时。
以下是一个通过代理模式控制客户访问权限的例子
class VideoSite // 主题,也是业务的基类,定义了各种业务的接口 { public: virtual void freeMovie() = 0; // 免费模式 virtual void vipMovie() = 0; // vip电影 virtual void ticketMovie() = 0; // 用券观看电影 }; class FixBugVideoSite : public VideoSite // 委托类,实现主题中的具体业务逻辑 { public: virtual void freeMovie() { cout << "观看免费电影" << endl; } virtual void vipMovie() { cout << "观看VIP电影" << endl; } virtual void ticketMovie() { cout << "用券观看电影" << endl; } }; // 代理类 代理免费客户 class FreeVideoSiteProxy : public VideoSite { public: FreeVideoSiteProxy() { pVideo = new FixBugVideoSite(); } ~FreeVideoSiteProxy() { delete pVideo; } virtual void freeMovie() { pVideo->freeMovie(); } virtual void vipMovie() { cout << "您目前只是普通游客,需要升级成VIP,才能观看VIP电影" << endl; } virtual void ticketMovie() { cout << "您目前只是普通游客,需要购买观影券,才能观看该电影" << endl; } private: VideoSite* pVideo; }; // 代理类 代理Vip客户 class VipVideoSiteProxy : public VideoSite { public: VipVideoSiteProxy() { pVideo = new FixBugVideoSite(); } ~VipVideoSiteProxy() { delete pVideo; } virtual void freeMovie() { pVideo->freeMovie(); } virtual void vipMovie() { pVideo->vipMovie(); } virtual void ticketMovie() { cout << "您目前只是普通游客,需要购买观影券,才能观看该电影" << endl; } private: VideoSite* pVideo; }; int main() { unique_ptr<VideoSite> p1(new FreeVideoSiteProxy()); p1->freeMovie(); p1->vipMovie(); p1->ticketMovie(); unique_ptr<VideoSite> p2(new VipVideoSiteProxy()); p2->freeMovie(); p2->vipMovie(); p2->ticketMovie(); }
装饰器模式
功能:在不改变原有类的基础上增加现有类的功能。
通常情况下,在不改变原有类的基础上增强现有类的功能可以通过子类继承现有类,重写接口或增加接口的方式来完成功能扩展,但是这样的话,对每个要增强功能的现有类都要增加子类。增加的类太多了!!!
装饰器模式的角色:
- 组件:抽象基类,定义了业务的接口,并且可以通过多态的方式作为接收具体组件对象和装饰器对象的数据类型。
- 具体组件:继承并实现了组件的具体业务逻辑。
- 装饰器抽象类:继承组件,也就是继承了组件中的业务接口。
- 具体装饰器:继承装饰器抽象类,并且构造时接收具体组件,实现业务接口,在实现的业务接口函数中,先调用具体组件的业务接口函数,然后增加拓展的功能。
相较于增加子类的方式增强现有类的功能,装饰器模式最大的好处是不必为每个现有类都增加子类,而是根据每个新增的功能增加装饰器类。
以下是一个装饰器模式的例子,其中省略了装饰器抽象类
class Car{ public: virtual void show() = 0; }; class Bmw : public Car { public: Bmw(string name = "宝马"):name(name) {} void show() { cout << name << ", 配置有: 基础配置"; } private: string name; }; class Audi : public Car { public: Audi(string name = "奥迪"):name(name) {} void show() { cout << name << ", 配置有: 基础配置"; } private: string name; }; class Benz : public Car { public: Benz(string name = "奥迪") :name(name) {} void show() { cout << name << ", 配置有: 基础配置"; } private: string name; }; // 装饰器1 定速巡航 class ConcreteDecorator01 : public Car { public: ConcreteDecorator01(Car* car):car(car){} void show() { car->show(); cout << ", 定速巡航"; } private: Car* car; }; // 装饰器2 自动刹车 class ConcreteDecorator02 : public Car { public: ConcreteDecorator02(Car* car) :car(car) {} void show() { car->show(); cout << ", 自动刹车"; } private: Car* car; }; int main() { Car* car0 = new Bmw(); car0->show(); cout << endl; Car* car1 = new ConcreteDecorator01(car0); car1->show(); cout << endl; Car* car2 = new ConcreteDecorator02(car1); car2->show(); cout << endl; delete car0; delete car1; delete car2; return 0; delete car0; delete car1; delete car2; return 0; }
输出结果:
宝马, 配置有: 基础配置 宝马, 配置有: 基础配置, 定速巡航 宝马, 配置有: 基础配置, 定速巡航, 自动刹车
适配器模式
作用:让不兼容的接口可以在一起工作
适配器模式的主要角色:
- 目标(Target)接口:这是客户期望使用的接口。
- 适配者 (Adaptee)类:这是需要被适配的类,它具有一些功能,但其接口与目标接口不兼容。
- 适配器 (Adapter)类:适配器类实现了目标接口,内部持有一个适配者对象,在目标接口函数中调用适配者对象的方法,达到适配者可以适配目标接口的目的。
// 表示VGA接口 class VGA { public: virtual void play() = 0; }; // TV01表示支持VGA接口的投影仪 class TV01 : public VGA { public: void play() { cout << "通过VGA接口连接投影仪,进行视频播放" << endl; } }; // 表示HDMI接口 class HDMI { public: virtual void play() = 0; }; // 一个只支持HDMI接口的投影仪 class TV02 : public HDMI { public: void play() { cout << "通过HDMI接口连接投影仪,进行视频播放" << endl; } }; // HDMI->VGA接口 适配器 class HDMI2VGAAdapter : public VGA { public: HDMI2VGAAdapter(weak_ptr<HDMI> tv):tv(tv){} void play() { auto stv = tv.lock(); cout << "HDMI->VGA适配器" << endl; stv->play(); } private: weak_ptr<HDMI> tv; }; // 一个只支持VGA接口的电脑类 class lenovoComputer { public: void playVideo(weak_ptr<VGA> pVGA) { shared_ptr<VGA> spVGA = pVGA.lock(); // 弱引用升级强引用 spVGA->play(); } }; int main() { shared_ptr<HDMI> tv(new TV02); // 创建支持HDMI接口的投影仪 shared_ptr<VGA> adapter(new HDMI2VGAAdapter(tv)); // 创建HDMI->VGA适配器 lenovoComputer computer; // 创建支持VGA的电脑 computer.playVideo(adapter); // 电脑通过适配器使用了支持HDMI接口的投影仪 return 0; return 0; }
computer.playVideo(adapter)调用了adapter→play(),adapter→play()调用tv→play(),通过在支持VGA的适配器内部调用支持 HDMI 的TV02对象,完成了接口的适配。
单例模式
一个类不管创建多少次,永远只能得到该类型一个对象的实例
单例模式分饿汉模式和懒汉模式
- 饿汉模式,在第一次获取该类的对象之前便已经创建好对象
- 懒汉模式,在第一次获取该类的对象时才创建对象
饿汉模式的优点:线程安全,因为静态成员变量在程序启动前就已经创建好了,即使多个线程同时获取对象,也没什么问题
饿汉模式的缺点:在程序启动时创建对象,会影响程序启动速度
懒汉模式的优点:在第一次获取对象时才创建对象,不会影响程序启动速度
懒汉模式的缺点:不是天然就线程安全的,不过可以写成线程安全的懒汉模式
以下是示例:
饿汉模式
// 饿汉式单例模式:还没获取实例对象,实例对象就已经产生了 class Singleton { public: static Singleton* getInstance() // 获取类的唯一实例对象的接口方法 { return &instance; } private: static Singleton instance; // 声明唯一的类实例对象(静态变量在类外定义) Singleton() // 构造函数私有化 { cout << "Singleton()" <<endl; } Singleton(Singleton&) = delete; // 删除拷贝函数和赋值运算符 Singleton operator=(Singleton&) = delete; }; Singleton Singleton::instance; // 定义静态成员变量
- 构造函数私有化
- 声明静态的类实例对象变量,别忘了在类外定义
- 提供获取类的实例对象的接口函数
- 删除拷贝函数和赋值运算符
线程不安全的懒汉模式
class Singleton { public: static Singleton* getInstance() // 获取类的唯一实例对象的接口方法 { if (instance == nullptr) { instance = new Singleton(); } return instance; } ~Singleton() { delete instance; } private: static Singleton* instance; // 声明唯一的类实例对象(静态变量在类外定义) Singleton() // 构造函数私有化 { cout << "Singleton()" << endl; } Singleton(Singleton&) = delete; // 删除拷贝函数和赋值运算符 Singleton operator=(Singleton&) = delete; }; Singleton* Singleton::instance = nullptr;
- 构造函数私有化
- 声明静态的类实例对象指针,类外将其定义并初始化位nullptr
- 提供获取类的实例对象的接口函数,在该函数中判断类实例对象是否存在,如果不存在就创建一个
- 删除拷贝函数和赋值运算符
线程安全的懒汉模式(使用互斥锁)
std::mutex mtx; class Singleton { public: static Singleton* getInstance() // 获取类的唯一实例对象的接口方法 { if (instance == nullptr) // 锁+双重判断 { lock_guard<std::mutex> guard(mtx); if (instance == nullptr) { instance = new Singleton(); } } return instance; } ~Singleton() { delete instance; } private: static Singleton* volatile instance; // 声明唯一的类实例对象(静态变量在类外定义) Singleton() // 构造函数私有化 { cout << "Singleton()" << endl; } Singleton(Singleton&) = delete; // 删除拷贝函数和赋值运算符 Singleton& operator=(Singleton&) = delete; }; Singleton* volatile Singleton::instance = nullptr;
在线程不安全的懒汉模式代码基础上,增加互斥锁和双重判断。对接收单例对象的静态指针变量增加volatile修饰,目的是确保多线程时使用该变量能够获取到最新的值,而不会因为编译器优化读取缓存中的值。
线程安全的懒汉模式(使用局部静态变量)
class Singleton { public: static Singleton* getInstance() // 获取类的唯一实例对象的接口方法 { static Singleton instance; // 静态局部变量初始化时,汇编指令上自动添加线程互斥指令 return &instance; } private: Singleton() // 构造函数私有化 { cout << "Singleton()" << endl; } Singleton(Singleton&) = delete; // 删除拷贝函数和赋值运算符 Singleton& operator=(Singleton&) = delete; };
静态局部变量只会在第一次初始化时有效,之后都会使用初始化之后的静态局部变量,并且编译器会在汇编指令中自动加入互斥指令。
观察者模式(Observer Pattern)
属于行为型模式:主要关注的是对象之间的通信
观察者-监听者模式(发布-订阅模式):主要关注的对象的一对多关系,也就是多个对象都依赖一个对象,当被依赖的对象的状态发生改变时,依赖它的对象都能够接收到相应的通知。
以下是一个观察者模式的实例,该例中,有三个观察者类Observer(1,2,3),以及一个主题类Subject,主题类提供了两个公有方法和一个私有方法。
公有方法:
- 一是添加观察者,这使得观察者可以观测主题,
- 二是改变主题,该方法可以改变主题内部的成员变量,同时调用通知观察者的方法。
私有方法:
- 通知观察者,这使得主题可以通知已添加的观察者,让观察者根据主题的改变而作出处理。
观察者类提供了一个handle方法,用来根据主题的状态进行相应的处理。
以上的类达到了这样一种效果,即改变Subject对象状态,会让依赖Subject对象的Observer对象作出相应的处理。
class Observer { public: virtual void handle(int msgid) = 0; }; class Observer1 : public Observer { public: void handle(int msgid) { switch (msgid) { case 1: cout << "Observer1 recv 1 msg!" << endl; break; case 2: cout << "Observer1 recv 2 msg!" << endl; break; default: cout << "Observer1 recv unknown msg!" << endl; break; } } }; class Observer2 : public Observer { public: void handle(int msgid) { switch (msgid) { case 2: cout << "Observer2 recv 2 msg!" << endl; break; default: cout << "Observer2 recv unknown msg!" << endl; break; } } }; class Observer3 : public Observer { public: void handle(int msgid) { switch (msgid) { case 1: cout << "Observer3 recv 1 msg!" << endl; break; case 3: cout << "Observer3 recv 3 msg!" << endl; break; default: cout << "Observer3 recv unknown msg!" << endl; break; } } }; class Subject { public: // 添加观察者 void addObserver(Observer* obser, int msgid) { _subMap[msgid].push_back(obser); } // 改变主题 void setMsgid(int msgid) { this->msgid = msgid; dispatch(msgid); } private: // 主题发生改变,通知相应的观察者对象处理事件 void dispatch(int msgid) { auto it = _subMap.find(msgid); if (it != _subMap.end()) { for (Observer* pObser : it->second) { pObser->handle(msgid); } } } unordered_map<int, list<Observer*>> _subMap; int msgid; }; int main() { Subject subject; Observer* p1 = new Observer1(); Observer* p2 = new Observer2(); Observer* p3 = new Observer3(); subject.addObserver(p1, 1); subject.addObserver(p1, 2); subject.addObserver(p2, 2); subject.addObserver(p3, 1); subject.addObserver(p3, 3); int msgid = 0; while (1) { cout << "输入消息id: " << endl; cin >> msgid; if (msgid == -1) { break; } subject.setMsgid(msgid); // 改变主题 } delete p1, p2, p3; return 0; // 改变主题 void setMsgid(int msgid) { this->msgid = msgid; dispatch(msgid); } subject.addObserver(p1, 1); subject.addObserver(p1, 2); subject.addObserver(p2, 2); subject.addObserver(p3, 1); subject.addObserver(p3, 3); int msgid = 0; while (1) { cout << "输入消息id: " << endl; cin >> msgid; if (msgid == -1) { break; } subject.setMsgid(msgid); // 改变主题 } delete p1, p2, p3; return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验