C++设计模式
一、什么叫C++的设计模式?
C++设计模式是指在软件工程中,特别是在使用C++语言进行面向对象编程时,针对常见问题的一系列经过验证的解决方案。设计模式不是具体的代码,而是用于指导软件设计和实现的高级模板和思想。设计模式可以帮助开发者避免重复发明轮子,提高代码的可重用性、可读性和可维护性。
设计模式通常分为三大类:
- 创建型模式(Creational Patterns):这些模式专注于对象的创建过程,同时隐藏创建逻辑,以便使程序在不修改现有代码的情况下更加灵活地引入新的对象。常见的创建型模式包括单例模式(Singleton)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)、建造者模式(Builder)和原型模式(Prototype)。
- 结构型模式(Structural Patterns):结构型模式关注如何通过不同的类和对象组合来形成更大的结构,以满足特定的设计需求。常见的结构型模式包括适配器模式(Adapter)、装饰器模式(Decorator)、代理模式(Proxy)、外观模式(Facade)、桥接模式(Bridge)、组合模式(Composite)和享元模式(Flyweight)。
- 行为型模式(Behavioral Patterns):行为型模式专注于对象间的交互以及它们如何相互协作以完成复杂的操作和算法。常见的行为型模式包括策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、迭代器模式(Iterator)、责任链模式(Chain of Responsibility)、命令模式(Command)、备忘录模式(Memento)、状态模式(State)和访问者模式(Visitor)。
- 设计模式的应用不仅限于C++,在其他面向对象的编程语言中也广泛使用。每种设计模式都有其特定的上下文和应用场景,合理地使用设计模式可以解决特定的设计问题,提高软件质量。
- 在C++中,设计模式的应用可能会涉及到语言特有的特性,如多态性、继承、模板和异常处理等。掌握设计模式对于C++开发者来说是一项重要的技能,有助于编写更加优雅和高效的代码。
二、UML类图
#include<iostream> #include<string> using namespace std; class Hunter { public: int m_age = 32; static int m_times; string getName() { return m_name; } void setName(string name) { m_name = name; } void goHunting() { aiming(); shoot(); } static void saySorry() { string count = to_string(m_times); cout << "Say sorry to every animal " + count + " times!" << endl; } protected: string m_name = "Jack"; void aiming() { cout << "使用" + m_gunName + "瞄准猎物..." << endl; } private: string m_gunName = "AK-47"; void shoot() { cout << "使用" + m_gunName + "射击猎物..." << endl; } }; int Hunter::m_times = 3;
可以看到该图分为上中下三部分:上层是类名,中间层是属性(类的成员变量),下层是方法(类的成员函数)。
可见性:+ 表示public、# 表示protected、- 表示private、__(下划线)表示static
属性的表示方式:【可见性】【属性名称】:【类型】= { 缺省值,可选 }
方法的表示方式:【可见性】【方法名称】(【参数名 : 参数类型,……】):【返回值类型】
三、简单工厂模式
1、factory 模式(工厂模式)
- Factory 模式(工厂模式)
#include <iostream> using namespace std; class AbstractSmile //父类 { public: virtual void transform() {}; virtual void ability() {}; virtual ~AbstractSmile() {}; }; // 人造恶魔果实· 绵羊形态 子类 class SheepSmile : public AbstractSmile { public: void transform() override { cout << "变成人兽 -- 山羊人形态..." << endl; } void ability() override { cout << "将手臂变成绵羊角的招式 -- 巨羊角" << endl; } }; // 人造恶魔果实· 狮子形态 class LionSmile : public AbstractSmile { public: void transform() override { cout << "变成人兽 -- 狮子人形态..." << endl; } void ability() override { cout << "火遁· 豪火球之术..." << endl; } }; class BatSmile : public AbstractSmile { public: void transform() override { cout << "变成人兽 -- 蝙蝠人形态..." << endl; } void ability() override { cout << "声纳引箭之万剑归宗..." << endl; } }; // 恶魔果实工厂类 enum class Type :char { SHEEP, LION, BAT }; class SmileFactory { public: SmileFactory() {}; ~SmileFactory() {}; AbstractSmile* createSmile(Type type) { AbstractSmile* ptr = nullptr; switch (type) { case Type::SHEEP: ptr = new SheepSmile; break; case Type::LION: ptr = new LionSmile; break; case Type::BAT: ptr = new BatSmile; break; default: break; } return ptr; } }; int main() { SmileFactory* factory = new SmileFactory; AbstractSmile* obj = factory->createSmile(Type::BAT); obj->transform(); obj->ability(); return 0; }
2、简单工厂函数说明
- 由于人造恶魔果实类有继承关系, 并且实现了多态,所以父类的析构函数也应该是虚函数,这样才能够通过父类指针或引用析构子类的对象。
- 工厂函数createSmile(Type type)的返回值修改成了AbstractSmile*类型,这是人造恶魔果实类的基类,通过这个指针保存的是子类对象的地址,这样就实现了多态,所以在main()函数中,通过obj对象调用的实际是子类BatSmile中的函数,因此打印出的信息应该是这样的:
3、工厂模式概括
工厂模式属于创建型模式之一,它用于处理对象的创建,将对象的创建与使用分离,以提高软件的灵活性和可扩展性。工厂模式允许通过接口来请求一个类的对象,而不是直接使用new操作符创建对象。
工厂模式有几种不同的形式,包括简单工厂模式、工厂方法模式和抽象工厂模式。
四、工厂模式
简单工厂模式违背了开发-封闭原则(比如枚举时需要加很多的case),简单工厂模式是只有一个工厂类,而工厂模式是有很多的工厂类:
- 一个基类,包含一个虚工厂函数,用于实现多态。
- 多个子类,重写父类的工厂函数。每个子工厂类负责生产一种恶魔果实,这相当于再次解耦,将工厂类的职责再次拆分、细化,如果要生产新品种的恶魔果实,那么只需要添加对应的工厂类,无需修改原有的代码。
恶魔果实和工厂均有一个基类(抽象类)和多个子类:
#include <iostream> using namespace std; class AbstractSmile { public: virtual void transform() = 0; virtual void ability() = 0; virtual ~AbstractSmile() {} }; // 人造恶魔果实· 绵羊形态 class SheepSmile : public AbstractSmile { public: void transform() override { cout << "变成人兽 -- 山羊人形态..." << endl; } void ability() override { cout << "将手臂变成绵羊角的招式 -- 巨羊角" << endl; } }; // 人造恶魔果实· 狮子形态 class LionSmile : public AbstractSmile { public: void transform() override { cout << "变成人兽 -- 狮子人形态..." << endl; } void ability() override { cout << "火遁· 豪火球之术..." << endl; } }; class BatSmile : public AbstractSmile { public: void transform() override { cout << "变成人兽 -- 蝙蝠人形态..." << endl; } void ability() override { cout << "声纳引箭之万剑归宗..." << endl; } }; class LGSmile :public AbstractSmile { public: void transform()override { cout << "变成offer收割机---大佬形态..." << endl; } void ability()override { cout << "我的期望薪资是25K一个月..." << endl; } }; // 恶魔果实工厂类 class AbstractFactory { public: virtual AbstractSmile* createSmile() = 0; virtual ~AbstractFactory() {} }; class SheepFactory : public AbstractFactory { public: AbstractSmile* createSmile() override { return new SheepSmile; } ~SheepFactory() { cout << "释放 SheepFactory 类相关的内存资源" << endl; } }; class LionFactory : public AbstractFactory { public: // 工厂函数 AbstractSmile* createSmile() override { return new LionSmile; } ~LionFactory() { cout << "释放 LionFactory 类相关的内存资源" << endl; } }; class BatFactory : public AbstractFactory { public: // 工厂函数 AbstractSmile* createSmile() override { return new BatSmile; } ~BatFactory() { cout << "释放 BatFactory 类相关的内存资源" << endl; } }; class LGFactory : public AbstractFactory { public: AbstractSmile* createSmile()override { return new LGSmile; } ~LGFactory() { cout << "释放 LGFactory 类相关的内存资源" << endl; } }; int main() { AbstractFactory* factory = new LGFactory; AbstractSmile* obj = factory->createSmile(); obj->transform(); obj->ability(); return 0; }
在上面的例子中,不论是恶魔果实的基类,还是工厂类的基类,它们的虚函数可以是纯虚函数,也可以是非纯虚函数。这样的基类在设计模式中就可以称之为抽象类(此处的抽象类和C++中对抽象类的定义有一点出入)。
五、抽象工厂模式
#include<iostream> using namespace std; // 船体 class ShipBody { public: virtual string getShipBody() = 0; virtual ~ShipBody() {} }; class WoodBody : public ShipBody { public: string getShipBody() override { return string("用<木材>制作轮船船体..."); } }; class IronBody : public ShipBody { public: string getShipBody() override { return string("用<钢铁>制作轮船船体..."); } }; class MetalBody : public ShipBody { public: string getShipBody() override { return string("用<合金>制作轮船船体..."); } }; // 动力 class Engine { public: virtual string getEngine() = 0; virtual ~Engine() {} }; class Human : public Engine { public: string getEngine() override { return string("使用<人力驱动>..."); } }; class Diesel : public Engine { public: string getEngine() override { return string("使用<内燃机驱动>..."); } }; class Nuclear : public Engine { public: string getEngine() override { return string("使用<核能驱动>..."); } }; // 武器 class Weapon { public: virtual string getWeapon() = 0; virtual ~Weapon() {} }; class Gun : public Weapon { public: string getWeapon() override { return string("配备的武器是<枪>"); } }; class Cannon : public Weapon { public: string getWeapon() override { return string("配备的武器是<自动机关炮>"); } }; class Laser : public Weapon { public: string getWeapon() override { return string("配备的武器是<激光>"); } }; // 轮船类 class Ship { public: Ship(ShipBody* body, Weapon* weapon, Engine* engine) : m_body(body), m_weapon(weapon), m_engine(engine) { } string getProperty() { string info = m_body->getShipBody() + m_weapon->getWeapon() + m_engine->getEngine(); return info; } ~Ship() { delete m_body; delete m_engine; delete m_weapon; } private: ShipBody* m_body = nullptr; Weapon* m_weapon = nullptr; Engine* m_engine = nullptr; }; // 工厂类 class AbstractFactory { public: virtual Ship* createShip() = 0; virtual ~AbstractFactory() {} }; class BasicFactory : public AbstractFactory { public: Ship* createShip() override { Ship* ship = new Ship(new WoodBody, new Gun, new Human); cout << "<基础型>战船生产完毕, 可以下水啦..." << endl; return ship; } }; class StandardFactory : public AbstractFactory { public: Ship* createShip() override { Ship* ship = new Ship(new IronBody, new Cannon, new Diesel); cout << "<标准型>战船生产完毕, 可以下水啦..." << endl; return ship; } }; class UltimateFactory : public AbstractFactory { public: Ship* createShip() override { Ship* ship = new Ship(new MetalBody, new Laser, new Nuclear); cout << "<旗舰型>战船生产完毕, 可以下水啦..." << endl; return ship; } }; int main() { AbstractFactory* factroy = new StandardFactory; Ship* ship = factroy->createShip(); cout << ship->getProperty(); delete ship; delete factroy; return 0; }
工厂模式区别总结:
- 简单工厂模式不能遵守开放-封闭原则,工厂和抽象工厂模式可以
- 简单工厂模式只有一个工厂类,工厂和抽象工厂有多个工厂类
- 工厂模式创建的产品对象相对简单,抽象工厂模式创建的产品对象相对复杂
- 工厂模式创建的对象对应的类不需要提供抽象类【这产品类组件中没有可变因素】
- 抽象工厂模式创建的对象对应的类有抽象的基类【这个产品类组件中有可变因素】
六、单例模式
在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。
单例模式是一种常用的设计模式,其核心思想是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式通常用于管理共享资源,比如配置信息、线程池、缓存等。
单例模式的特点:
- 唯一性:一个类只有一个实例。
- 全局访问点:提供一种方式来获取这个唯一的实例。
- 线程安全:在多线程环境中,单例的创建和访问需要是线程安全的。
单例模式的实现方式:
单例模式有多种实现方式,主要包括懒汉式、饿汉式、双重检查锁定、静态内部类等。
1. 懒汉式(线程不安全)
class Singleton { public: static Singleton* getInstance() { if (instance == nullptr) { instance = new Singleton(); } return instance; } private: Singleton() {} static Singleton* instance; }; Singleton* Singleton::instance = nullptr;
这种方式简单直观,但存在线程安全问题。
2. 饿汉式(线程安全)
class Singleton { public: static Singleton* getInstance() { return &instance; } private: Singleton() {} static Singleton instance; }; Singleton instance; // 静态成员变量
这种方式在程序启动时就创建实例,确保线程安全,但可能浪费资源,因为实例可能一直未被使用。
3. 懒汉式(线程安全)
class Singleton { public: static Singleton* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mutex); if (instance == nullptr) { instance = new Singleton(); } } return instance; } private: Singleton() {} static Singleton* instance; static std::mutex mutex; }; Singleton* Singleton::instance = nullptr; std::mutex Singleton::mutex;
这种方式通过互斥锁确保线程安全,但存在性能问题,因为每次获取实例时都需要加锁。
4. 双重检查锁定(线程安全,性能好)
class Singleton { public: static Singleton* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mutex); if (instance == nullptr) { instance = new Singleton(); } } return instance; } private: Singleton() {} static Singleton* instance; static std::mutex mutex; }; Singleton* Singleton::instance = nullptr; std::mutex Singleton::mutex;
这种方式通过双重检查锁定来减少锁的使用,提高性能。
5. 静态内部类(线程安全,延迟加载)
class Singleton { private: static class InstanceHolder { public: static Singleton instance; } holder; public: static Singleton* getInstance() { return &InstanceHolder::instance; } private: Singleton() {} }; class Singleton::InstanceHolder { public: Singleton instance; };
这种方式利用静态内部类的特性,实现延迟加载和线程安全。
使用场景:
- 需要全局访问点的资源管理,如配置信息。
- 需要限制实例数量的对象,如数据库连接池。
注意事项:
- 单例模式可能会隐藏类之间的依赖关系,使得代码难以测试和维护。
- 单例对象的生命周期与应用程序相同,可能会造成资源浪费。
- 在多线程环境中,需要特别注意线程安全问题。
在自动驾驶和机器人领域,C++因其高性能、内存管理高效和跨平台兼容性等特性,被广泛应用。本专栏整理了C++面试中常遇到的八股问题,可私信作者要飞书文档,不论是嵌入式软开、算法、软件开发都可以阅读,包括了C++的虚函数、C++11新特性、C++的STL库、Linux常见命令......