C++ 日志系统实战第三步:熟悉掌握各种设计模式
全是通俗易懂的讲解,如果你本节之前的知识都掌握清楚,那就速速来看我的项目笔记吧~
相关技术知识补充,也是最后的补充知识了~ 下文将加入项目代码编写!
目录
设计模式
单例模式
饿汉模式
懒汉模式
工厂模式
简单工厂模式
工厂方法模式
抽象工厂模式
建造者模式
代理模式
设计模式
设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
六大原则:
- 单一职责原则(Single Responsibility Principle)
- 原则内容:类的职责应该单一,一个方法只做一件事。职责划分清晰,每次改动到最小单位的方法或类。
- 使用建议:两个完全不一样的功能不应该放一个类中,一个类中应该是一组相关性很高的函数、数据的封装。
- 用例:网络聊天:网络通信 & 聊天,应该分割成为网络通信类 & 聊天类。
- 开闭原则(Open Closed Principle)
- 原则内容:对扩展开放,对修改封闭。
- 使用建议:对软件实体的改动,最好用扩展而非修改的方式。
- 用例:超时卖货:商品价格 - 不是修改商品的原来价格,而是新增促销价格。
- 里氏替换原则(Liskov Substitution Principle)
- 原则内容:通俗点讲,就是只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。在继承类时,务必重写父类中所有的方法,尤其需要注意父类的 protected 方法,子类尽量不要暴露自己的 public 方法供外界调用 。
- 使用建议:子类必须完全实现父类的方法,子类可以有自己的个性。覆盖或实现父类的方法时,输入参数可以被放大,输出可以缩小。
- 用例:跑步运动员类 - 会跑步,子类长跑运动员 - 会跑步且擅长长跑,子类短跑运动员 - 会跑步且擅长短跑。
- 依赖倒置原则(Dependence Inversion Principle)
- 原则内容:高层模块不应该依赖低层模块,两者都应该依赖其抽象,不可分割的原子逻辑就是低层模式,原子逻辑组装成的就是高层模块。模块间依赖通过抽象(接口)发生,具体类之间不直接依赖。
- 使用建议:每个类都尽量有抽象类,任何类都不应该从具体类派生。尽量不要重写基类的方法。结合里氏替换原则使用。
- 用例:奔驰车司机类 - 只能开奔驰; 司机类 - 给什么车,就开什么车; 开车的人:司机 - 依赖于抽象。
- 迪米特法则(Law of Demeter),又叫 “最少知道法则”
- 原则内容:尽量减少对象之间的交互,从而减小类之间的耦合。一个对象应该对其他对象有最少的了解。对类的低耦合提出了明确的要求:只和直接的朋友交流,朋友之间也是有距离的。自己的就是自己的(如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中 )。
- 使用建议:无特殊额外建议。
- 用例:老师让班长点名 - 老师给班长一个名单,班长完成点名勾选,返回结果,而不是班长点名,老师勾选。
- 接口隔离原则(Interface Segregation Principle)
- 原则内容:客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。
- 使用建议:接口设计尽量精简单一,但是不要对外暴露没有实际意义的接口。
- 用例:修改密码,不应该提供修改用户信息接口,而就是单一的最小修改密码接口,更不要暴露数据库操作。
从整体上来理解六大设计原则,可以简要的概括为一句话,用抽象构建框架,用实现扩展细节,具体到每一条设计原则,则对应一条注意事项:
- 单一职责原则告诉我们实现类要职责单一;
- 里氏替换原则告诉我们不要破坏继承体系;
- 依赖倒置原则告诉我们要面向接口编程;
- 接口隔离原则告诉我们在设计接口的时候要精简单一;
- 迪米特法则告诉我们要降低耦合;
- 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。
我们了解设计模式之后,我们来了解具体的设计模式吧
单例模式
单例模式的定义:“一个类只能创建一个对象,即单例模式。该设计模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享”。这表明单例模式是一种特殊的设计模式,在这种模式下,一个类在整个程序运行过程中只能有一个对象(实例)存在。并且有一个固定的、全局都能访问到的方式去获取这个唯一的实例,同时这个实例会被程序里的各个模块所使用。
举例说明:“在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理” 。
- 在这个服务器程序的场景里,服务器的配置信息都在一个文件中。
- 我们创建一个单例对象,这个单例对象的职责就是去读取这个配置文件里的信息。由于它是单例的,整个程序中只有这么一个对象能做这件事,保证了配置信息读取的唯一性和一致性。
- 而服务进程中的其他对象,如果它们需要用到这些配置信息,不需要各自去读取文件(这样可能会导致重复读取或者读取不一致等问题),只需要通过访问这个单例对象,就能获取到配置信息。
- 这样做的好处就是,在比较复杂的服务器程序环境中,对配置信息的管理变得更加简单和高效。因为所有关于配置信息的操作都围绕着这一个单例对象进行,避免了多个对象同时操作配置信息可能带来的混乱。
例如,想象有多个模块都需要读取数据库的连接配置信息。如果没有单例模式,每个模块可能都要去读取配置文件,可能会出现读取错误、配置不一致等问题。而使用单例模式,有一个专门的单例对象读取配置文件中的数据库连接信息,其他模块都从这个单例对象获取信息,就能保证整个系统使用的数据库连接配置是一致的,也便于对配置进行修改和维护。
单例模式有两种实现模式:饿汉模式和懒汉模式。
饿汉模式
在 C++ 里,饿汉模式是单例模式的一种实现方式。单例模式的目的是保证一个类仅有一个实例,并提供一个全局访问点来获取该实例。
饿汉模式的特点
- 实例创建时机:在程序开始运行时就创建单例类的实例,而不是在首次使用时才创建。“饿汉” 就像一个饥饿的人,迫不及待地创建实例。
- 线程安全:由于实例在程序启动时就已经创建好,所以不存在多线程环境下同时创建多个实例的问题,天生具备线程安全性。
示例代码
下面是一个简单的饿汉模式实现示例:
#include <iostream>class Singleton {
private:// 构造函数私有化,防止外部创建对象Singleton() {}// 拷贝构造函数私有化,防止拷贝Singleton(const Singleton&) = delete;// 赋值运算符私有化,防止赋值Singleton& operator=(const Singleton&) = delete;// 静态成员变量,存储单例实例static Singleton instance;public:// 静态成员函数,用于获取单例实例static Singleton& getInstance() {return instance;}void doSomething() {std::cout << "Singleton is doing something." << std::endl;}
};// 初始化静态成员变量
Singleton Singleton::instance;int main() {// 获取单例实例Singleton& singleton = Singleton::getInstance();// 调用实例的方法singleton.doSomething();return 0;
}
代码解释
- 构造函数私有化:
Singleton
类的构造函数被声明为私有,这样外部代码就无法直接创建Singleton
类的对象。- 静态成员变量:
static Singleton instance;
是一个静态成员变量,用于存储单例实例。在类外进行了初始化Singleton Singleton::instance;
,这确保了在程序启动时就创建了实例。- 静态成员函数:
static Singleton& getInstance()
是一个静态成员函数,用于获取单例实例。由于它是静态的,可以直接通过类名调用。- 拷贝构造函数和赋值运算符删除:为了防止通过拷贝或赋值操作创建新的实例,将拷贝构造函数和赋值运算符声明为
delete
。
使用步骤
- 定义单例类:按照上述示例,将构造函数、拷贝构造函数和赋值运算符私有化,并定义一个静态成员变量和一个静态成员函数。
- 初始化静态成员变量:在类外对静态成员变量进行初始化。
- 获取单例实例:通过调用静态成员函数
getInstance()
来获取单例实例,并调用实例的方法。
饿汉模式的优点是实现简单且线程安全,但缺点是如果单例实例占用资源较多,而程序可能并不需要使用该实例,会造成资源浪费。
懒汉模式
在 C++ 中,懒汉模式同样是单例模式的一种实现方式。单例模式的核心是确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。
懒汉模式的特点
- 实例创建时机:懒汉模式与饿汉模式不同,它是在首次使用单例实例时才进行创建。就像一个 “懒人”,不到万不得已不会去创建实例。
- 线程安全问题:在多线程环境下,如果不进行特殊处理,可能会出现多个线程同时判断实例未创建,从而各自创建一个实例的情况,导致单例模式失效。所以,在多线程环境中使用懒汉模式需要考虑线程安全问题。
简单懒汉模式(非线程安全)
以下是一个简单的非线程安全的懒汉模式实现示例:
#include <iostream>class Singleton {
private:// 构造函数私有化,防止外部创建对象Singleton() {}// 拷贝构造函数私有化,防止拷贝Singleton(const Singleton&) = delete;// 赋值运算符私有化,防止赋值Singleton& operator=(const Singleton&) = delete;// 静态成员指针,用于存储单例实例static Singleton* instance;public:// 静态成员函数,用于获取单例实例static Singleton* getInstance() {if (instance == nullptr) {instance = new Singleton();}return instance;}void doSomething() {std::cout << "Singleton is doing something." << std::endl;}
};// 初始化静态成员指针
Singleton* Singleton::instance = nullptr;int main() {// 获取单例实例Singleton* singleton = Singleton::getInstance();// 调用实例的方法singleton->doSomething();return 0;
}
代码解释
- 构造函数私有化:
Singleton
类的构造函数被声明为私有,这样外部代码就无法直接创建Singleton
类的对象。- 静态成员指针:
static Singleton* instance;
是一个静态成员指针,用于存储单例实例。初始化为nullptr
,表示还未创建实例。- 静态成员函数:
static Singleton* getInstance()
是一个静态成员函数,用于获取单例实例。在函数内部,首先检查instance
是否为nullptr
,如果是,则创建一个新的Singleton
实例;否则,直接返回已有的实例。- 拷贝构造函数和赋值运算符删除:为了防止通过拷贝或赋值操作创建新的实例,将拷贝构造函数和赋值运算符声明为
delete
。
线程安全的懒汉模式(双重检查锁定)
在多线程环境中,可以使用双重检查锁定(Double-Checked Locking)来实现线程安全的懒汉模式:
#include <iostream>
#include <mutex>class Singleton {
private:// 构造函数私有化,防止外部创建对象Singleton() {}// 拷贝构造函数私有化,防止拷贝Singleton(const Singleton&) = delete;// 赋值运算符私有化,防止赋值Singleton& operator=(const Singleton&) = delete;// 静态成员指针,用于存储单例实例static Singleton* instance;// 静态互斥锁,用于线程同步static std::mutex mtx;public:// 静态成员函数,用于获取单例实例static Singleton* getInstance() {if (instance == nullptr) {std::lock_guard<std::mutex> lock(mtx);if (instance == nullptr) {instance = new Singleton();}}return instance;}void doSomething() {std::cout << "Singleton is doing something." << std::endl;}
};// 初始化静态成员指针
Singleton* Singleton::instance = nullptr;
// 初始化静态互斥锁
std::mutex Singleton::mtx;int main() {// 获取单例实例Singleton* singleton = Singleton::getInstance();// 调用实例的方法singleton->doSomething();return 0;
}
代码解释
- 双重检查:在
getInstance()
函数中,首先进行一次非锁定检查,判断instance
是否为nullptr
。如果不为nullptr
,则直接返回实例,避免了不必要的锁竞争。如果为nullptr
,则加锁进行第二次检查,确保在加锁期间没有其他线程已经创建了实例。- 互斥锁:使用
std::mutex
来实现线程同步,确保在同一时间只有一个线程可以进入临界区创建实例。
使用步骤
- 定义单例类:按照上述示例,将构造函数、拷贝构造函数和赋值运算符私有化,并定义一个静态成员指针和一个静态成员函数。
- 初始化静态成员指针:在类外对静态成员指针进行初始化,初始值为
nullptr
。- 获取单例实例:通过调用静态成员函数
getInstance()
来获取单例实例,并调用实例的方法。
懒汉模式的优点是在需要时才创建实例,避免了不必要的资源浪费;缺点是实现相对复杂,特别是在多线程环境中需要考虑线程安全问题。
工厂模式
工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象时不会对上层暴露创建逻辑,而是通过使用一个共同结构来指向新创建的对象,以此实现创建 - 使用的分离。
工厂模式可以分为:
简单工厂模式
- 简单工厂模式:简单工厂模式实现由一个工厂对象通过类型决定创建出来指定产品类的实例。假设有个工厂能生产出水果,当客户需要产品的时候明确告知工厂生产哪类水果,工厂需要接收用户提供的类别信息,当新增产品的时候,工厂内部去添加新产品的生产方式。
简单工厂模式代码示例
// 简单工厂模式: 通过参数控制可以生产任何产品
// 优点: 简单粗暴,直观易懂。使用一个工厂生产同一等级结构下的任意产品
// 缺点:
// 1. 所有东西生产在一起,产品太多会导致代码量庞大
// 2. 开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须修改工厂方法。#include <iostream>
#include <string>
#include <memory>class Fruit {
public:Fruit() {}virtual void show() = 0;
};class Apple : public Fruit {
public:Apple() {}virtual void show() {std::cout << "我是一个苹果" << std::endl;}
};class Banana : public Fruit {
public:Banana() {}virtual void show() {std::cout << "我是一个香蕉" << std::endl;}
};class FruitFactory {
public:static std::shared_ptr<Fruit> create(const std::string &name) {if (name == "苹果") {return std::make_shared<Apple>();} else if (name == "香蕉") {return std::make_shared<Banana>();}return std::shared_ptr<Fruit>();}
};int main() {std::shared_ptr<Fruit> fruit = FruitFactory::create("苹果");fruit->show();fruit = FruitFactory::create("香蕉");fruit->show();return 0;
}
这个模式的结构和管理产品对象的方式十分简单,但是它的扩展性非常差,当我们需要新增产品的时候,就需要去修改工厂类新增一个类型的产品创建逻辑,违背了开闭原则。
工厂方法模式
工厂方法模式是在简单工厂模式基础上的扩展,在该模式下新增多个工厂,每个产品对应一个工厂。例如有 A、B 两种产品,就分别开设工厂 A 负责生产产品 A ,工厂 B 负责生产产品 B 。用户只需知道产品对应的工厂名,无需了解具体产品信息,工厂也只需专注生产,无需接收客户对产品类别的指定。
代码示例及注释
#include <iostream>
#include <string>
#include <memory>// 水果基类,定义了抽象方法show(),后续具体水果类需实现该方法,用于展示水果相关信息
class Fruit {
public:Fruit() {}virtual void show() = 0;
};// 苹果类,继承自Fruit基类,实现了show()方法,用于输出苹果相关信息
class Apple : public Fruit {
public:Apple() {}virtual void show() {std::cout << "我是一个苹果" << std::endl;}
private:std::string _color; // 私有成员变量,可用于表示苹果颜色等信息
};// 香蕉类,继承自Fruit基类,实现了show()方法,用于输出香蕉相关信息
class Banana : public Fruit {
public:Banana() {}virtual void show() {std::cout << "我是一个香蕉" << std::endl;}
private:std::string _color; // 私有成员变量,可用于表示香蕉颜色等信息
};// 水果工厂抽象基类,定义了抽象的create()方法,用于创建水果对象,具体实现由子类完成
class FruitFactory {
public:virtual std::shared_ptr<Fruit> create() = 0;
};// 苹果工厂类,继承自FruitFactory,重写create()方法,用于创建苹果对象
class AppleFactory : public FruitFactory {
public:virtual std::shared_ptr<Fruit> create() {return std::make_shared<Apple>(); // 创建并返回一个苹果对象的智能指针}
};// 香蕉工厂类,继承自FruitFactory,重写create()方法,用于创建香蕉对象
class BananaFactory : public FruitFactory {
public:virtual std::shared_ptr<Fruit> create() {return std::make_shared<Banana>(); // 创建并返回一个香蕉对象的智能指针}
};int main() {// 创建一个指向AppleFactory对象的智能指针,这里使用AppleFactory来生产苹果std::shared_ptr<FruitFactory> factory(new AppleFactory());// 通过苹果工厂创建一个水果对象(实际是苹果对象)std::shared_ptr<Fruit> fruit = factory->create();// 调用苹果对象的show()方法,输出"我是一个苹果"fruit->show();// 重置factory指针,使其指向BananaFactory对象,准备生产香蕉factory.reset(new BananaFactory());// 通过香蕉工厂创建一个水果对象(实际是香蕉对象)fruit = factory->create();// 调用香蕉对象的show()方法,输出"我是一个香蕉"fruit->show();return 0;
}
模式优缺点
- 优点:
- 减轻了工厂类的负担,将某类产品的生产交给指定的工厂来进行 。
- 开闭原则遵循较好,添加新产品只需要新增产品的工厂即可,不需要修改原先的工厂类 。
- 缺点:对于某种可以形成一组产品族的情况处理较为复杂,需要创建大量的工厂类。每次增加一个产品时,都需要增加一个具体产品类和工厂类,这会使得系统中类的个数成倍增加,在一定程度上增加了系统的耦合度。
抽象工厂模式
抽象工厂模式是在工厂方法模式基础上的进一步拓展。工厂方法模式虽引入工厂等级结构,解决了简单工厂模式中工厂类职责过重问题,但每个工厂只生产一类产品,可能导致系统中工厂类数量庞大,开销增加。抽象工厂模式的基本思想是把一些相关产品组成一个产品族(即位于不同产品等级结构中功能相关联的产品集合 ),由同一个工厂来统一生产。
代码示例及注释
#include <iostream>
#include <string>
#include <memory>// 水果基类,定义抽象方法show(),用于展示水果相关信息,具体由子类实现
class Fruit {
public:Fruit() {}virtual void show() = 0;
};// 苹果类,继承自Fruit基类,实现show()方法,用于输出苹果相关信息
class Apple : public Fruit {
public:Apple() {}virtual void show() {std::cout << "我是一个苹果" << std::endl;}
private:std::string _color; // 私有成员变量,可用于表示苹果颜色等信息
};// 香蕉类,继承自Fruit基类,实现show()方法,用于输出香蕉相关信息
class Banana : public Fruit {
public:Banana() {}virtual void show() {std::cout << "我是一个香蕉" << std::endl;}
private:std::string _color; // 私有成员变量,可用于表示香蕉颜色等信息
};// 动物基类,定义抽象方法voice(),用于发出动物叫声,具体由子类实现
class Animal {
public:virtual void voice() = 0;
};// 羊类,继承自Animal基类,实现voice()方法,输出羊的叫声
class Lamb : public Animal {
public:void voice() {std::cout << "咩咩咩\n";}
};// 狗类,继承自Animal基类,实现voice()方法,输出狗的叫声
class Dog : public Animal {
public:void voice() {std::cout << "汪汪汪\n";}
};// 工厂抽象基类,定义了创建水果和动物对象的抽象方法,具体实现由子类完成
class Factory {
public:virtual std::shared_ptr<Fruit> getFruit(const std::string &name) = 0;virtual std::shared_ptr<Animal> getAnimal(const std::string &name) = 0;
};// 水果工厂类,继承自Factory,实现创建水果对象的方法
class FruitFactory : public Factory {
public:// 该方法在当前类中不创建动物对象,直接返回空指针virtual std::shared_ptr<Animal> getAnimal(const std::string &name) {return std::shared_ptr<Animal>();}virtual std::shared_ptr<Fruit> getFruit(const std::string &name) {if (name == "苹果") {return std::make_shared<Apple>(); // 创建并返回一个苹果对象的智能指针} else if (name == "香蕉") {return std::make_shared<Banana>(); // 创建并返回一个香蕉对象的智能指针}return std::shared_ptr<Fruit>(); // 若名称不匹配,返回空的智能指针}
};// 动物工厂类,继承自Factory,实现创建动物对象的方法
class AnimalFactory : public Factory {
public:// 该方法在当前类中不创建水果对象,直接返回空指针virtual std::shared_ptr<Fruit> getFruit(const std::string &name) {return std::shared_ptr<Fruit>();}virtual std::shared_ptr<Animal> getAnimal(const std::string &name) {if (name == "小羊") {return std::make_shared<Lamb>(); // 创建并返回一个羊对象的智能指针} else if (name == "小狗") {return std::make_shared<Dog>(); // 创建并返回一个狗对象的智能指针}return std::shared_ptr<Animal>(); // 若名称不匹配,返回空的智能指针}
};// 工厂生产者类,用于根据传入的名称获取对应的工厂对象
class FactoryProducer {
public:static std::shared_ptr<Factory> getFactory(const std::string &name) {if (name == "动物") {return std::make_shared<AnimalFactory>(); // 返回动物工厂对象的智能指针} else {return std::make_shared<FruitFactory>(); // 返回水果工厂对象的智能指针}}
};int main() {// 获取水果工厂对象std::shared_ptr<Factory> fruit_factory = FactoryProducer::getFactory("水果");// 通过水果工厂创建苹果对象并调用其show()方法std::shared_ptr<Fruit> fruit = fruit_factory->getFruit("苹果");fruit->show();// 通过水果工厂创建香蕉对象并调用其show()方法fruit = fruit_factory->getFruit("香蕉");fruit->show();// 获取动物工厂对象std::shared_ptr<Factory> animal_factory = FactoryProducer::getFactory("动物");// 通过动物工厂创建羊对象并调用其voice()方法std::shared_ptr<Animal> animal = animal_factory->getAnimal("小羊");animal->voice();// 通过动物工厂创建狗对象并调用其voice()方法animal = animal_factory->getAnimal("小狗");animal->voice();return 0;
}
模式优缺点
- 优点:
- 可以将一组相关的产品对象的创建封装在一起,提高了代码的内聚性和可维护性 。
- 当需要创建多个相关产品时,减少了创建对象的复杂程度 。
- 缺点:
- 增加新的产品等级结构复杂,若要添加新的产品族,需要对抽象层代码进行较大修改,违背了 “开闭原则” 。
建造者模式
建造者模式是一种创建型设计模式,它利用多个简单对象逐步构建出一个复杂对象,能够将复杂对象的构建过程和其表示形式相分离,为创建对象提供了一种优化方式,主要用于应对对象构建过程过于繁杂的情况。
建造者模式的四个核心类
- 抽象产品类:定义了产品的抽象属性和行为。
- 具体产品类:是具体的产品对象类,继承自抽象产品类,实现了具体的产品功能。
- 抽象 Builder 类:为创建产品对象所需的各个部件定义了抽象接口。
- 具体产品的 Builder 类:实现抽象 Builder 类的接口,负责具体构建各个部件。
- 指挥者 Director 类:统一管理组装过程,提供给调用者使用,通过指挥者来构建完整的产品。
下面通过一个简单的电脑组装示例,使用 C++ 代码来解释建造者模式。在这个示例中,我们要构建不同配置的电脑,包括主板和显示器等组件。
#include <iostream>
#include <memory>/*抽象电脑类*/
class Computer {public:using ptr = std::shared_ptr<Computer>;Computer() {}void setBoard(const std::string &board) {_board = board;}void setDisplay(const std::string &display) {_display = display;}virtual void setOs() = 0;std::string toString() {std::string computer = "Computer:{\n";computer += "\tboard=" + _board + ",\n"; computer += "\tdisplay=" + _display + ",\n"; computer += "\tOs=" + _os + ",\n"; computer += "}\n";return computer;}protected:std::string _board;std::string _display;std::string _os;
};/*具体产品类*/
class MacBook : public Computer {public:using ptr = std::shared_ptr<MacBook>;MacBook() {}virtual void setOs() {_os = "Max Os X12";}
};/*抽象建造者类:包含创建一个产品对象的各个部件的抽象接口*/
class Builder {public:using ptr = std::shared_ptr<Builder>;virtual void buildBoard(const std::string &board) = 0;virtual void buildDisplay(const std::string &display) = 0;virtual void buildOs() = 0;virtual Computer::ptr build() = 0;
};/*具体产品的具体建造者类:实现抽象接口,构建和组装各个部件*/
class MackBookBuilder : public Builder {public:using ptr = std::shared_ptr<MackBookBuilder>;MackBookBuilder(): _computer(new MacBook()) {}virtual void buildBoard(const std::string &board) {_computer->setBoard(board);}virtual void buildDisplay(const std::string &display) {_computer->setDisplay(display);}virtual void buildOs() {_computer->setOs();}virtual Computer::ptr build() {return _computer;}private:Computer::ptr _computer;
};/*指挥者类,提供给调用者使用,通过指挥者来获取产品*/
class Director {public:Director(Builder* builder):_builder(builder){}void construct(const std::string &board, const std::string &display) {_builder->buildBoard(board);_builder->buildDisplay(display);_builder->buildOs();}private:Builder::ptr _builder;
};int main()
{Builder *buidler = new MackBookBuilder();std::unique_ptr<Director> pd(new Director(buidler));pd->construct("英特尔主板", "VOC显示器");Computer::ptr computer = buidler->build();std::cout << computer->toString();return 0;
}
代码解释
抽象产品类
Computer
:
- 定义了电脑的基本属性,包括主板(
_board
)、显示器(_display
)和操作系统(_os
)。- 提供了设置主板和显示器的方法
setBoard
和setDisplay
。- 包含一个纯虚函数
setOs
,这使得Computer
成为抽象类,具体的电脑产品类需要实现该方法来设置操作系统。toString
方法用于将电脑的配置信息格式化为字符串并返回,方便输出展示。具体产品类
MacBook
:
- 继承自
Computer
类,实现了setOs
方法,将操作系统设置为"Max Os X12"
。抽象建造者类
Builder
:
- 定义了创建电脑各个部件的抽象接口,包括
buildBoard
(构建主板)、buildDisplay
(构建显示器)、buildOs
(构建操作系统)以及build
(获取构建好的电脑对象)。具体产品的具体建造者类
MackBookBuilder
:
- 继承自
Builder
类,在构造函数中创建了一个MacBook
对象。- 实现了
buildBoard
、buildDisplay
和buildOs
方法,分别调用MacBook
对象的相应设置方法来构建电脑的各个部件。build
方法返回构建好的MacBook
对象。指挥者类
Director
:
- 接收一个
Builder
对象,通过construct
方法调用Builder
对象的构建方法,完成电脑的组装过程。
main
函数:
- 创建了一个
MackBookBuilder
对象。- 创建
Director
对象并传入MackBookBuilder
对象。- 调用
Director
的construct
方法,传入主板和显示器的信息,完成电脑的构建。- 调用
MackBookBuilder
的build
方法获取构建好的电脑对象。- 输出电脑的配置信息。
通过这种方式,建造者模式将电脑的构建过程和表示分离,使得我们可以方便地构建不同配置的电脑,同时也提高了代码的可维护性和可扩展性。
代理模式
代理模式指代理控制对其他对象的访问,也就是代理对象控制对原对象的引用。在某些情况下,一个对象不适合或者不能直接被引用访问,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的结构包括一个是真正的你要访问的对象 (目标类)、一个是代理对象。目标对象与代理对象实现同一个接口,先访问代理类再通过代理类访问目标对象。代理模式分为静态代理、动态代理:
- 静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
- 动态代理指的是,在运行时才动态生成代理类,并将其与被代理类绑定。这意味着,在运行时才能确定代理类要代理的是哪个被代理类。
以租房为例,房东将房子租出去,但是要租房子出去,需要发布招租启示,带人看房,负责维修,这些工作中有些操作并非房东能完成,因此房东为了图省事,将房子委托给中介进行租赁。代理模式实现:
// 以房东通过中介租房子为例理解代理模式
#include <iostream>
#include <string>// 抽象接口类,定义租房的抽象行为
class RentHouse {
public:// 纯虚函数,用于定义租房的行为,具体实现由子类完成virtual void rentHouse() = 0;
};// 房东类,继承自RentHouse接口,代表真实的目标对象
class Landlord : public RentHouse {
public:// 实现RentHouse接口中的rentHouse方法,描述房东将房子租出去的行为void rentHouse() {std::cout << "将房子租出去\n"; }
};// 中介类,继承自RentHouse接口,作为代理对象
class Intermediary : public RentHouse {
public:// 实现RentHouse接口中的rentHouse方法void rentHouse() {// 中介在代理租房前可以添加一些额外操作,比如发布招租信息等std::cout << "中介发布招租信息\n"; // 调用房东实际的租房操作_landlord.rentHouse(); // 中介在代理租房后可以添加一些额外操作,比如安排后续事宜等std::cout << "中介安排后续事宜\n"; }
private:// 持有房东对象,用于在代理行为中调用房东的实际租房操作Landlord _landlord;
};int main() {Intermediary intermediary;intermediary.rentHouse();return 0;
}
如果你对日志系统感到兴趣,欢迎关注我👉【A charmer】
后续我将继续带你实现日志系统~
相关文章:
C++ 日志系统实战第三步:熟悉掌握各种设计模式
全是通俗易懂的讲解,如果你本节之前的知识都掌握清楚,那就速速来看我的项目笔记吧~ 相关技术知识补充,也是最后的补充知识了~ 下文将加入项目代码编写! 目录 设计模式 单例模式 饿汉模式 懒汉模式 工厂模式 简单…...
[ESP-IDF]:esp32-camera 使用指南 ESP32S3-OV2640 用例测试
【核知坊】:释放青春想象,码动全新视野。 我们希望使用精简的信息传达知识的骨架,启发创造者开启创造之路!!! 内容摘要:esp32-camera 组件为 ESP32 系列 SoC 提供了兼容的图…...
在统信UOS/麒麟Kylin OS中创建网页桌面快捷方式
在统信UOS/麒麟Kylin OS中创建网页桌面快捷方式 本文将详细介绍如何在统信UOS或麒麟KYLINOS中使用命令行创建一个网页桌面快捷方式,以方便构建云桌面模板及镜像模板。欢迎大家浏览、分享和转发!请关注我以获取更多技术分享。 1. 查看系统信息 首先&am…...
SQLite 是什么?
📌 一、SQLite 是什么? SQLite 是一个轻量级、嵌入式数据库,意思是它直接集成在你的 App 内部,不需要单独安装数据库服务端。 ✅ 特点: 特点说明本地使用所有数据保存在手机内部存储文件形式数据以 .db 文件形式存储…...
恒创科技「香港大带宽云」新老用户专享实例及热门配置
全球化数字浪潮下,高带宽应用正深度重构各行业运营模式——从跨境电商、流媒体与视频点播,到在线游戏与云游戏加速,涵盖所有高并发、强交互的业务场景。在此背景下,企业对高性能 IT 基础架构的需求持续升级,以此来支持…...
fpga系列 HDL:verilog latch在fpga中的作用 避免latch的常见做法
目录 Latch在FPGA中的作用Quartus中有关latch的警告⚠避免Latch的常见做法1. if-else 语句未覆盖所有条件生成Latch的代码:修复后的代码: 2. case语句未覆盖所有分支生成Latch的代码:修复后的代码: 3. 组合逻辑中缺少默认赋值生成…...
java配置
环境变量...
解决虚拟主机ping不通本地主机问题
win11 1 问题 虚拟主机和本地主机在同一网段。 2 解决方案 以win11为例: 设置 -> 网络和 Internet -> 高级网路设置 -> Windows 防火墙 -> 高级设置 -> 入站规则 -> 新建规则 需要设置:规则类型、 协议和端口、名称,其…...
Move Registry 发布,实现 Sui 的超级互操作性
Move Registry(MVR)的到来对 Sui 来说是一件大事。MVR 是一个功能齐全的链上包管理系统,提升了整个生态的可发现性、可信度和互操作性。Sui 本身就是最具互操作性的链之一,凭借 Move 语言和可编程交易区块(PTBs&#x…...
【Linux】gdb工具,Linux 下程序调试的 “透视眼”
目录 调试代码调试注意事项gdb和Cgdb调试命令汇总行号显示断点设置查看断点信息删除断点开启 / 禁用断点运行 / 调试逐过程和逐语句打印 / 追踪变量指定行号跳转强制执行函数 补充命令watchset var 替换变量值条件断点 end 调试代码 这是本次调试要用的代码 1 #include <st…...
脚本分享:快速作图对比wannier拟合能带python脚本
本脚本通过Python实现电子能带结构数据的快速作图,能够从两个不同的数据文件(BAND.dat 和 wannier90_band.dat)中提取有效数据,并在同一坐标系下绘制对比图。 准备工作:使用VASPKIT处理获得能带数据BAND.datÿ…...
解决ssh拉取服务器数据,要多次输入密码的问题
问题在于,每次循环调用 rsync 都是新开一个连接,所以每次都需要输入一次密码。为了只输入一次密码,有以下几种方式可以解决: ✅ 推荐方案:设置 SSH 免密登录 最稳最安全的方式是:配置 SSH 免密登录&#x…...
金仓数据库 KingbaseES 产品深度优化提案:迈向卓越的全面升级
文章目录 一、引言二、性能优化(一)查询性能提升1. 优化查询优化器引入基于代价的查询优化算法支持更多的查询优化提示 2. 索引优化支持更多类型的索引优化索引的创建和维护策略 (二)并发处理能力增强1. 锁机制优化采用更细粒度的…...
企业级智能合同管理解决方案升级报告:道本科技携手DeepSeek打造智能合同管理新标杆
当传统合同管理系统还在与堆积如山的纸质文档较劲时,道本科技与DeepSeek联合开发的智能合同平台已为国央企打开新视界。我们以某大型能源集团的实际应用为例,带您直观感受技术升级带来的管理变革。 一、技术升级的具象化呈现 在未接入DeepSeek技术前&a…...
C#并行编程极大提升集合处理速度,再也没人敢说你程序性能差了!
马工撰写的年入30万C#上位机项目实战必备教程(点击下方链接即可访问文章目录) 1、《C#串口通信从入门到精通》 2、《C#与PLC通信从入门到精通 》 3、《C# Modbus通信从入门到精通》 4、《C#Socket通信从入门到精通 》 5、《C# MES通信从入门到精通》 6、…...
[贪心_7] 最优除法 | 跳跃游戏 II | 加油站
目录 1.最优除法 题解 2.跳跃游戏 II 题解 3.加油站 题解 利用 单调性,可以实现 区间跳跃 1.最优除法 链接: 553. 最优除法 给定一正整数数组 nums,nums 中的相邻整数将进行浮点除法。 例如,nums [2,3,4],我…...
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...
【CUDA 编译 bug】ld: cannot find -lcudart
我们使用 Conda 安装 pytorch 和 CUDA 环境之后,要用 Conda 的CUDA环境进行某个库编译时,出现了bug: /mnt/data/home/xxxx/miniforge3/envs/GAGAvatar/compiler_compat/ld: cannot find -lcudart: No such file or directorycollect2: error…...
MYSQL之数据类型
数据类型分类 数值类型 在MySQL中, 整型可以指定是有符号的和无符号的, 默认是有符号的. 可以通过 UNSIGNED 来说明某个字段是无符号的. tinyint类型 以tinyint为例, 其它的整型类型都只是数据范围的区别. 数据越界 创建一个 tinyint 类型的 num 的属性, 大小为 1 字节, 不…...
Asp.Net Core 异常筛选器ExceptionFilter
文章目录 前言一、异常筛选器的核心概念用途:实现接口:执行时机: 二、使用步骤1.创建自定义异常筛选器2.注册异常筛选器全局注册(对所有 Controller 生效):局部注册(通过特性标记特定的 **Contr…...
WebUI可视化:第2章:技术基础准备
学习目标 ✅ 掌握HTML/CSS基础语法 ✅ 理解JavaScript核心功能 ✅ 了解前后端交互原理 2.1 HTML基础:网页的骨架 2.1.1 基础结构 每个HTML文件都必须包含以下基本结构: html <!DOCTYPE html> <html> <head><title>我的第一个网页</title> …...
Java基础集合 面试经典八股总结 [连载ing]
序言 八股,怎么说呢。我之前系统学习的内容,进行梳理。通过问题的方式,表达出得当的内容,这件事本身就很难。面试时心态、状态、掌握知识的情况等。关于八股文,我不想有太多死记硬背的内容,更多的是希望自我…...
大数据运维面试题
华为大数据运维面试题可能涵盖多个方面,以下是一些可能的面试问题及解析,这些问题旨在考察应聘者的技术知识、问题解决能力和对大数据运维的理解: 一、技术知识类问题 简述大数据运维的主要职责和工作内容 回答示例:大数据运维工…...
OpenBMC:BmcWeb login认证
BmcWeb在include\login_routes.hpp中实现了/login用于完成web的登录: BMCWEB_ROUTE(app, "/login").methods(boost::beast::http::verb::post)(handleLogin);inline void handleLogin(const crow::Request& req,const std::shared_ptr<bmcweb::AsyncResp>…...
Python学习之路(五)-接口API
在 Python 中结合数据库开发接口 API 通常使用 Web 框架(如 Flask 或 Django)和 ORM(对象关系映射)工具(如 SQLAlchemy 或 Django ORM)。以下是使用 Flask 和 SQLAlchemy 的详细步骤,展示如何结合数据库开发一个简单的 API。 使用 Flask 和 SQLAlchemy 开发 API 1. 安…...
数据库+Docker+SSH三合一!深度评测HexHub的全栈开发体验
作为一名技术博主,我最近一直被各种开发工具切换搞得焦头烂额。数据库要用Navicat,服务器管理得开Termius,Docker操作还得切到命令行,每天光在不同工具间切换就浪费了大量时间。直到团队里的一位架构师向我推荐了HexHub这个一体化…...
涂料油墨制造数字化转型的关键技术与挑战
涂料油墨制造行业正处于数字化转型的关键时期,这一转型是提升生产效率、增强产品质量和降低成本的重要途径。以下是该行业在数字化转型中的关键技术与面临的挑战: 关键技术: 工业互联网技术:通过在生产设备上安装传感器…...
UE5 调整字体、界面大小
文章目录 方案一 5.4 版本及以上(推荐)方案二 5.3 版本及以下(推荐)方案三 使用插件(不推荐) 方案一 5.4 版本及以上(推荐) 进入 编辑 > 编辑器偏好设置,如下图所示&…...
【OpenCV图像处理实战】从基础操作到工业级应用
目录 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心代码实现(6个案例)案例1:图像基本操作案例2:边缘检测案例3&…...
生成随机验证码-解析与优化
文章目录 代码功能解析潜在问题与优化建议1. 安全性问题2. 易混淆字符过滤3. 参数校验4. 性能优化 扩展功能示例1. 自定义字符集2. 批量生成验证码 完整优化代码关键总结 代码功能解析 import random import stringdef generate_code(length6):chars string.digits string.a…...
VMware 虚拟机镜像资源网站
常见的 VMware 虚拟机镜像资源网站 网站名称链接地址特点OSBoxes.orgOSBoxes - Virtual Machines for VirtualBox & VMware 提供 .vmx .vmdk,适合 VMware 和 VirtualBox,更新频率高,界面清晰LinuxVMImages.comLinux VM Images - Downlo…...
HTML5 详细学习笔记
1. HTML5 简介 HTML5 是最新的 HTML 标准,于 2014 年 10 月由 W3C 完成标准制定。它增加了许多新特性,包括语义化标签、多媒体支持、图形效果、离线存储等。 1.1 HTML5 文档基本结构 <!DOCTYPE html> <html lang"zh-CN"> <h…...
真.从“零”搞 VSCode+STM32CubeMx+C <1>构建
目录 前言 准备工作 创建STM32CubeMx项目 VSCode导入项目&配置 构建错误调试 后记 前言 去年10月开始接触单片机,一直在用树莓派的Pico,之前一直用Micropython,玩的不亦乐乎,试错阶段优势明显,很快就能鼓捣一…...
Pikachu靶场
本质是信任了不可信的客户端输入。防御核心: 永不信任客户端提交的权限参数(如 user_id, role)。强制服务端校验用户身份与操作权限。定期审计权限模型,避免业务迭代引入新漏洞。 水平越权 1,按照网站的提示要求登录 进…...
五、web自动化测试01
目录 一、HTML基础1、HTML介绍2、常用标签3、基础案例3.1 前端代码3.2 自动化测试 二、CSS定位1、css介绍2、案例3、代码优化 三、表单自动化1、案例2、元素属性定位 四、后台基础数据自动化1、登录1.1 id与class定位1.2 定位一组元素 2、商品新增 一、HTML基础 可参考学习 链…...
利用软件I2C驱动OLED,点亮、熄灭OLED屏幕以及获取当前OLED屏幕开启状态
题目: 参考《I2C通信》的文档,自行连接电路,利用软件I2C驱动OLED,点亮、熄灭OLED屏幕以及获取当前OLED屏幕开启状态。 可以优先实现: 软件I2C初始化函数,用于初始化IO引脚 再实现: 主机发起始位和停止位,主机发送1个字节,…...
数据结构——栈与队列
1.栈 1.1概念 一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO ( Last In First Out )的原则。 压栈:栈…...
【嵌入式系统设计师(软考中级)】第二章:嵌入式系统硬件基础知识(3)
文章目录 4. 嵌入式系统I/O接口4.1 GPIO与PWM接口4.1.1 GPIO接口(General-Purpose Input/Output)4.1.2 PWM接口(Pulse Width Modulation) 4.2 A/D与D/A接口的基本原理与结构4.2.1 DA转换(数模转换,Digital-…...
【网络安全】社会工程学策略
1. 社会工程学简介 社会工程攻击是威胁行为者常用的攻击方式。这是因为,诱骗人们提供访问权限、信息或金钱通常比利用软件或网络漏洞更容易。 您可能还记得,社会工程学是一种利用人为错误来获取私人信息、访问权限或贵重物品的操纵技术。它是一个涵盖性…...
ROS2---时间戳对齐
一、ROS2时间系统架构 时间模型 仿真时间(Simulation Time):由/clock话题驱动,适用于离线仿真与调试。真实时间(Real Time):基于系统硬件时钟,支持PTP协议(IEEE 1588&…...
C语言教程(十五):C 语言函数指针与回调函数详解
一、函数指针 1.1 函数指针的概念 在 C 语言中,函数指针是指向函数的指针变量。每个函数在内存中都有一个起始地址,函数指针就存储了这个起始地址,通过函数指针可以调用相应的函数。 1.2 函数指针的定义 函数指针的定义语法如下:返…...
VSCode如何修改默认扩展路径和用户文件夹目录到其他盘以及微信开发工具如何修改扩展路径到其他盘
ps:因公司电脑c盘内存严重不足,而出本篇文章 1.Visual Studio Code 随着VsCode的使用时间的推移,安装的扩展以及数据逐步增多,导致c盘内存占用较大,所以这里将vscode的默认缓存路径等迁移到其他盘。 步骤如下 1.找到默认的存储…...
抽象类相关
抽象类的定义 抽象类 是一种特殊的类,它不能被实例化,只能作为基类来派生出具体类。抽象类至少包含一个纯虚函数 。纯虚函数是在函数原型前加上 0 的虚函数,表示该函数没有具体实现,必须由派生类来实现。 抽象类的作用 提供统…...
如何测试短信接口
目录 一、测试短信接口的基本流程 1. 了解短信接口文档 2. 使用工具测试短信接口 示例一:用 curl 测试 POST 请求 示例二:用 Postman 设置 POST 请求 3. 编写测试脚本(Python 示例) 二、测试类型和场景 ✅ 正常发送测试 …...
pycharm2024.3.2项目解释器选择问题
问题描述:已经选择了pyau虚拟环境的解释器,运行了conda activate pyau,但是为什么关闭pycharm2024.3.2软件重新启动后,打开终端是(base) PS D:\deepseektest> ,为什么不是(pyau) PS D:\deepseektest> 解决问题&a…...
如何在 Dialog 中安全初始化 ECharts 并自动监听容器大小变化
如何在 Dialog 中安全初始化 ECharts 并自动监听容器大小变化 在使用 ECharts 的 Vue 项目中,我们常常会将图表放入弹窗(如 Element UI 的 <el-dialog>)中进行展示。但你是否遇到过以下问题: 图表初次显示尺寸异常&#x…...
如何借助ETL数据集成工具实现数据一致性?
主要可以从以下几个方面入手: 一、数据抽取阶段(Extract) 统一数据源连接方式:ETL工具通常支持多种数据源连接方式,如关系型数据库、非关系型数据库、文件系统、API接口等。在抽取数据时,要确保对各个数据…...
3.4/Q1,GBD数据库最新文章解读
文章题目:Burden of Carbon Monoxide Poisoning in Asian Countries From 1990 to 2021 and Its Projection Until 2030: An Analysis of the Global Burden of Disease Study 2021 DOI:10.2147/CLEP.S512786 中文标题:1990 年至 2021 年亚洲…...
【高中数学/古典概率】4红2黑六选二,求取出两次都是红球的概率
【问题】 袋子里装4只红球,2只黑球,大小完全相同,抽两次球,每次抽一只,抽出后不再放回,求取出的两次都是红球的概率。 【来源】 数林外传系列之《概率与期望》P20 单埻著 中国科学技术大学出版社 【数学…...
机器人操作中的生成式 AI:综述(上)
25年3月来自香港大学、香港理工、香港科大、浙大和清华大学的论文“Generative Artificial Intelligence in Robotic Manipulation: A Survey”。 本综述全面回顾机器人操作领域生成学习模型的最新进展,并探讨该领域的关键挑战。机器人操作面临着关键瓶颈ÿ…...