C++基于多设计模式下的同步异步日志系统day1

news2024/11/20 1:53:07

C++基于多设计模式下的同步&异步日志系统day1

📟作者主页:慢热的陕西人

🌴专栏链接:C++基于多设计模式下的同步&异步日志系统

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

主要内容记录了,日志项目需要的一些前置的补充知识。

在这里插入图片描述

文章目录

  • C++基于多设计模式下的同步&异步日志系统day1
    • 1.项目介绍
      • 1.1项目功能
      • 1.2开发环境
      • 1.3核心技术
      • 1.4环境搭建
    • 2.日志系统的介绍
      • 2.1什么是日志?
      • 2.2为什么需要日志系统
      • 2.3日志系统的技术实现
        • 2.3.1同步写日志
        • 2.3.2异步写日志
    • 3.相关技术知识补充
      • 3.1不定参函数
    • 4.设计模式
      • 4.1六大原则
      • 4.2单例模式
      • 4.3工厂模式
      • 4.4⼯⼚⽅法模式
      • 4.5抽象⼯⼚模式
      • 4.6建造者模式
      • 4.7代理模式

1.项目介绍

1.1项目功能

本项⽬主要实现⼀个⽇志系统,其主要⽀持以下功能:

  • ⽀持多级别⽇志消息
  • ⽀持同步⽇志和异步⽇志
  • ⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中
  • ⽀持多线程程序并发写⽇志
  • ⽀持扩展不同的⽇志落地⽬标地

1.2开发环境

  • CentOS7
  • vscode/vim
  • g++/gdb
  • Makefile

1.3核心技术

  • 类层次设计(继承和多态的应⽤)
  • C++11(多线程、auto、智能指针、右值引⽤等)
  • 双缓冲区
  • ⽣产消费模型
  • 多线程
  • 设计模式(单例、⼯⼚、代理、模板等)

1.4环境搭建

本项目不依赖于其他任何第三方库,只需要安装好CentOS/Ubuntu+vscode/vim环境即可开发。

2.日志系统的介绍

2.1什么是日志?

**日志:**程序运行过程中所记录的程序运行状态信息。

  • 作用:记录了程序的运行状态信息,以便于程序员能够随时根据状态信息,对系统的运行状态,进行分析

2.2为什么需要日志系统

  • ⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题,可以借助⽇
    志系统来打印⼀些⽇志帮助开发⼈员解决问题
  • 上线客⼾端的产品出现bug⽆法复现并解决,可以借助⽇志系统打印⽇志并上传到服务端帮助开发
    ⼈员进⾏分析
  • 对于⼀些⾼频操作(如定时器、⼼跳包)在少量调试次数下可能⽆法触发我们想要的⾏为,通过断
    点的暂停⽅式,我们不得不重复操作⼏⼗次、上百次甚⾄更多,导致排查问题效率是⾮常低下,可
    以借助打印⽇志的⽅式查问题
  • 在分布式、多线程/多进程代码中,出现bug⽐较难以定位,可以借助⽇志系统打印log帮助定位
    bug
  • 帮助⾸次接触项⽬代码的新开发⼈员理解代码的运⾏流程

2.3日志系统的技术实现

⽇志系统的技术实现主要包括三种类型 :

  • 利⽤printf、std::cout等输出函数将⽇志信息打印到控制台

  • 对于⼤型商业化项⽬,为了⽅便排查问题,我们⼀般会将⽇志输出到⽂件或者是数据库系统⽅便查
    询和分析⽇志,主要分为同步⽇志和异步⽇志⽅式

    a.同步写⽇志

    b.异步写⽇志

2.3.1同步写日志

同步⽇志是指当输出⽇志时,必须等待⽇志输出语句执⾏完毕后,才能执⾏后⾯的业务逻辑语句,⽇
志输出语句与程序的业务逻辑语句将在同⼀个线程运⾏。每次调⽤⼀次打印⽇志API就对应⼀次系统调
⽤write写⽇志⽂件。

image-20240229150136540

在⾼并发场景下,随着⽇志数量不断增加,同步⽇志系统容易产⽣系统瓶颈:

  • ⼀⽅⾯,⼤量的⽇志打印陷⼊等量的write系统调⽤,有⼀定系统开销.
  • 另⼀⽅⾯,使得打印⽇志的进程附带了⼤量同步的磁盘IO,影响程序性能.
2.3.2异步写日志

异步⽇志是指在进⾏⽇志输出时,⽇志输出语句与业务逻辑语句并不是在同⼀个线程中运⾏,⽽是有专⻔的线程⽤于进⾏⽇志输出操作。业务线程只需要将⽇志放到⼀个内存缓冲区中不⽤等待即可继续执⾏后续业务逻辑(作为⽇志的⽣产者),⽽⽇志的落地操作交给单独的⽇志线程去完成(作为⽇志的消费者),这是⼀个典型的⽣产-消费模型

image-20240229151158018

这样做的好处是即使⽇志没有真的地完成输出也不会影响程序的主业务,可以提⾼程序的性能:

  • 主线程调⽤⽇志打印接⼝成为⾮阻塞操作
  • 同步的磁盘IO从主线程中剥离出来交给单独的线程完成

3.相关技术知识补充

3.1不定参函数

在初学C语⾔的时候,我们都⽤过printf函数进⾏打印。其中printf函数就是⼀个不定参函数,在函数内
部可以根据格式化字符串中格式化字符分别获取不同的参数进⾏数据的格式化。

不定参宏函数

#include<iostream>
#include<cstdarg>

#define LOG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
//其中最后一个参数__VA_ARGS__,前的##作用是当我们只有一个参数的时候,编译器自动忽略从最后一个","开始的内容;
int main()
{
    LOG("%s-%s", "hello", "西安邮电大学");
    return 0;
}

C风格不定参函数

①一个打印数字的例子:

#include<iostream>
#include<cstdarg>

void printNum(int n, ...)
{
    va_list ap;
    va_start(ap, n); //使得ap指向参数n后面的第一个可变参数
    for(int i = 0; i < n; ++i)
    {
        int num = va_arg(ap, int); //从可变参数中获取每一个整形参数
        std::cout << num << " ";
    }
    std::cout << std::endl;
    va_end(ap); //清空可变参数列表--->将ap置空,防止内存泄漏
}


int main()
{
    printNum(2, 1, 2);
    printNum(5, 78, 25, 37, 43, 55);
    return 0;
}

//运行效果:
//[mi@lavm-5wklnbmaja lesson1]$ ./args 
//1 2 
//78 25 37 43 55 

②实现一个printf

#include<iostream>
#include<cstdarg>

void myprintf(const char* fmt, ...)
{
    char *res;
    va_list ap;
    va_start(ap, fmt); 
    int ret = vasprintf(&res, fmt, ap); //调用vasprintf函数将不定参列表中的参数按照fmt提供的格式,整合成一个字符串;
    if(ret != -1)  //vasprintf函数失败的时候返回-1
    std::cout << res << std::endl;
    va_end(ap);
    free(res);
}

int main()
{
    myprintf("西安邮电大学");
    myprintf("%s-%d", "西安邮电大学",666);
    return 0;
}

//运行效果:
//[mi@lavm-5wklnbmaja lesson1]$ ./args 
//西安邮电大学
//西安邮电大学-666

C++风格不定参函数

#include<iostream>
#include<cstdarg>

void xprintf()
{
    std::cout << std::endl;
}
template<typename T, typename ...Args>
void xprintf(const T & val, Args &&...args) //这里使用右值引用目的是为了使用完美转发
{
    std::cout << val << " ";
    if((sizeof ...(args)) > 0)
    {
        xprintf(std::forward<Args>(args)...); //完美转发,传参的过程中保留对象原生类型属性
    }
    else 
    {
        xprintf();
    }
}

int main()
{
    xprintf("西安邮电大学");
    xprintf("西安邮电大学", "666");
    xprintf("西安邮电大学", "666", "旭日苑");
    return 0;
}

//运行效果
//[root@lavm-5wklnbmaja lesson1]# ./args 
//西安邮电大学 
//西安邮电大学 666 
//西安邮电大学 666 旭日苑 

4.设计模式

4.1六大原则

  • 单一职责原则(SingleResponsibilityPrinciple );

    a.类的职责应该单⼀,⼀个⽅法只做⼀件事。职责划分清晰了,每次改动到最⼩单位的⽅法或类。

    b.使⽤建议:两个完全不⼀样的功能不应该放⼀个类中,⼀个类中应该是⼀组相关性很⾼的函数、数据的封装。

    c.例子:⽹络聊天:⽹络通信&聊天,应该分割成为⽹络通信类&聊天类

  • 开闭原则(OpenClosedPrinciple )

    a.对扩展开放,对修改封闭

    b.使⽤建议:对软件实体的改动,最好⽤扩展⽽⾮修改的⽅式

    c.⽤例:超时卖货:商品价格—不是修改商品的原来价格,⽽是新增促销价格

  • 里氏替换原则(LiskovSubstitutionPrinciple )

    a.通俗点讲,就是只要⽗类能出现的地⽅,⼦类就可以出现,⽽且替换为⼦类也不会产⽣任何错误或异常

    b.在继承类时,务必重写⽗类中所有的⽅法,尤其需要注意⽗类的protected⽅法,⼦类尽量不要暴露⾃⼰的public⽅法供外界调⽤

    c.使⽤建议:⼦类必须完全实现⽗类的⽅法,孩⼦类可以有⾃⼰的个性。覆盖或实现⽗类的⽅法时,输⼊参数可以被放⼤,输出可以缩⼩

    d.⽤例:跑步运动员类-会跑步,⼦类⻓跑运动员-会跑步且擅⻓⻓跑,⼦类短跑运动员-会跑步且擅⻓短跑

  • 依赖倒置原则(Dependence Inversion Principle)

​ a.⾼层模块不应该依赖低层模块,两者都应该依赖其抽象.不可分割的原⼦逻辑就是低层模式,原⼦逻辑组装成的就是⾼层模块

​ b.模块间依赖通过抽象(接⼝)发⽣,具体类之间不直接依赖

​ c.使⽤建议:每个类都尽量有抽象类,任何类都不应该从具体类派⽣。尽量不要重写基类的⽅法。结合⾥⽒替换原则使⽤

​ d.⽤例:奔驰⻋司机类–只能开奔驰;司机类–给什么⻋,就开什么⻋;开⻋的⼈:司机–依赖于抽象

  • 迪⽶特法则(LawofDemeter),⼜叫“最少知道法则”

    a.尽量减少对象之间的交互,从⽽减⼩类之间的耦合。⼀个对象应该对其他对象有最少的了解。
    对类的低耦合提出了明确要求:

    ​ 1.只和直接的朋友交流,朋友之间也是有距离的。⾃⼰的就是⾃⼰的(如果⼀个⽅法放在本类中,既不增加类间关系,也对本类不产⽣负⾯影响,那就放置在本类中)

    b.⽤例:⽼师让班⻓点名–⽼师给班⻓⼀个名单,班⻓完成点名勾选,返回结果,⽽不是班⻓点名,⽼师勾选

  • 接⼝隔离原则(Interface Segregation Principle );

​ a.客⼾端不应该依赖它不需要的接⼝,类间的依赖关系应该建⽴在最⼩的接⼝上

​ b.使⽤建议:接⼝设计尽量精简单⼀,但是不要对外暴露没有实际意义的接⼝

​ c.⽤例:修改密码,不应该提供修改⽤⼾信息接⼝,⽽就是单⼀的最⼩修改密码接⼝,更不要暴露数据库操作

从整体上来理解六⼤设计原则,可以简要的概括为⼀句话,⽤抽象构建框架,⽤实现扩展细节,具体
到每⼀条设计原则,则对应⼀条注意事项:

  • 单⼀职责原则告诉我们实现类要职责单⼀;
  • ⾥⽒替换原则告诉我们不要破坏继承体系;
  • 依赖倒置原则告诉我们要⾯向接⼝编程;
  • 接⼝隔离原则告诉我们在设计接⼝的时候要精简单⼀;
  • 迪⽶特法则告诉我们要降低耦合;
  • 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭;

4.2单例模式

⼀个类只能创建⼀个对象,即单例模式,该设计模式可以保证系统中该类只有⼀个实例,并提供⼀个访问它的全局访问点,该实例被所有程序模块共享。⽐如在某个服务器程序中,该服务器的配置信息存放在⼀个⽂件中,这些配置数据由⼀个单例对象统⼀读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种⽅式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式

  • 饿汉模式:程序启动时就会创建⼀个唯⼀的实例对象。因为单例对象已经确定,所以⽐较适⽤于多
    线程环境中,多线程获取单例对象不需要加锁,可以有效的避免资源竞争,提⾼性能。
#include<iostream>
#include<cstdarg>

//饿汉
class Singleton
{
private:
    Singleton()
    {
        std::cout << "创建了Singleton对象" << std::endl;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& Getinstance()
{
    return instance;
}
    ~Singleton()
    {}

private:
    int num;
    static Singleton instance;
};

Singleton Singleton::instance;

int main()
{
    return 0;
}

  • 懒汉模式:第⼀次使⽤要使⽤单例对象的时候创建实例对象。如果单例对象构造特别耗时或者耗费济源(加载插件、加载⽹络资源等),可以选择懒汉模式,在第⼀次使⽤的时候才创建对象。

    a.这⾥介绍的是《EffectiveC++》⼀书作者ScottMeyers提出的⼀种更加优雅简便的单例模式Meyers'SingletoninC++

    b.C++11Staticlocalvariables特性以确保C++11起,静态变量将能够在满⾜thread-safe的前提下唯⼀地被构造和析构。

#include<iostream>
#include<cstdarg>

class Singleton
{
private:
    Singleton()
    {
        std::cout << "创建了Singleton对象" << std::endl;
    }
    Singleton(const Singleton &) = delete;
    Singleton &operator=(const Singleton &) = delete;

public:
    static Singleton &Getinstance()
    {
        static Singleton instance;
        return instance;
    }
    ~Singleton()
    {
    }

private:
    int num;
    static Singleton instance;
};

int main()
{
    Singleton::Getinstance();
    Singleton::Getinstance();
    Singleton::Getinstance();
    Singleton::Getinstance();
    return 0;
}

4.3工厂模式

⼯⼚模式是⼀种创建型设计模式,它提供了⼀种创建对象的最佳⽅式。在⼯⼚模式中,我们创建对象时不会对上层暴露创建逻辑,⽽是通过使⽤⼀个共同结构来指向新创建的对象,以此实现创建-使⽤的分离。

工厂模式可以分为:

  • 简单工厂模式:

    简单⼯⼚模式实现由⼀个⼯⼚对象通过类型决定创建出来指定产品类的实例。假设有个⼯⼚能⽣产出⽔果,当客⼾需要产品的时候明确告知⼯⼚⽣产哪类⽔果,⼯⼚需要接收⽤⼾提供的类别信息,当新增产品的时候,⼯⼚内部去添加新产品的⽣产⽅式 。

    优点:简单粗暴,直观易懂。使⽤⼀个⼯⼚⽣产同⼀等级结构下的任意产品

    缺点:1.所有东西⽣产在⼀起,产品太多会导致代码量庞⼤

    ​ 2.开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须修改⼯⼚⽅法

#include<iostream>
#include<cstdarg>
#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;
}

这个模式的结构和管理产品对象的⽅式⼗分简单,但是它的扩展性⾮常差,当我们需要新增产品的时
候,就需要去修改⼯⼚类新增⼀个类型的产品创建逻辑,违背了开闭原则

4.4⼯⼚⽅法模式

在简单⼯⼚模式下新增多个⼯⼚,多个产品,每个产品对应⼀个⼯⼚。假设现在有A、B两种产品,则开两个⼯⼚,⼯⼚A负责⽣产产品A,⼯⼚B负责⽣产产品B,⽤⼾只知道产品的⼯⼚名,⽽不知道具体的产品信息,⼯⼚不需要再接收客⼾的产品类别,⽽只负责⽣产产品。

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:
    virtual std::shared_ptr<Fruit> create() = 0;
};

class AppleFactory : public FruitFactory
{
public:
    std::shared_ptr<Fruit> create() override
    {
        return std::make_shared<Apple>();
    }
};

class BananaFactory : public FruitFactory
{
public:
    std::shared_ptr<Fruit> create() override
    {
        return std::make_shared<Banana>();
    }
};

int main()
{
    std::shared_ptr<FruitFactory> factory(new AppleFactory());
    std::shared_ptr<Fruit>fruit = factory->create();
    fruit->show();
    factory.reset(new BananaFactory());
    fruit = factory->create();
    fruit->show();
    return 0;
}


⼯⼚⽅法模式每次增加⼀个产品时,都需要增加⼀个具体产品类和⼯⼚类,这会使得系统中类的个数
成倍增加,在⼀定程度上增加了系统的耦合度。

4.5抽象⼯⼚模式

⼯⼚⽅法模式通过引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责太重的问题,但由于⼯⼚⽅法模式中的每个⼯⼚只⽣产⼀类产品,可能会导致系统中存在⼤量的⼯⼚类,势必会增加系统的开销。此时,我们可以考虑将⼀些相关的产品组成⼀个产品族(位于不同产品等级结构中功能相关联的产品组成的家族),由同⼀个⼯⼚来统⼀⽣产,这就是抽象⼯⼚模式的基本思想。

#include<iostream>
#include<cstdarg>
#include<string>
#include<memory>

class Fruit
{
public:
    Fruit() {}
    virtual void name() = 0;
};

class Apple : public Fruit
{
public:
    Apple() {}
    virtual void name()
    {
        std::cout << "我是一个苹果" << std::endl;
    }
};

class Banana : public Fruit
{
public:
    Banana() {}
    virtual void name()
    {
        std::cout << "我是一个香蕉" << std::endl;
    }
};

class Animal
{
public:
    virtual void name() = 0; //纯虚函数,要求子类必须重写
};

class Lamp : public Animal
{
public:
    virtual void name()
    {
        std::cout << "我是山羊!!" << std::endl;
    }
};

class Dog : public Animal
{
public:
    virtual void name()
    {
        std::cout << "我是小狗!!" << std::endl;
    }
};

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;
};

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>(); //相当于空指针;
    }
};

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<Lamp>();
        }
        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> ff = FactoryProducer::getFactory("水果");
    std::shared_ptr<Fruit> f = ff->GetFruit("香蕉");
    f->name();
    f = ff->GetFruit("苹果");
    f->name();

    std::shared_ptr<Factory> af = FactoryProducer::getFactory("动物");
    std::shared_ptr<Animal> a = af->GetAnimal("山羊");
    a->name();
    a = af->GetAnimal("小狗");
    a->name();

    return 0;
}

//运行结果
//[mi@lavm-5wklnbmaja PreStudy]$ ./args 
//我是一个香蕉
//我是一个苹果
//我是山羊!!
//我是小狗!!

抽象⼯⼚模式适⽤于⽣产多个⼯⼚系列产品衍⽣的设计模式,增加新的产品等级结构复杂,需要对原有系统进⾏较⼤的修改,甚⾄需要修改抽象层代码,违背了“开闭原则”

4.6建造者模式

建造者模式是⼀种创建型设计模式,使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀个复杂的对象的构建与它的表⽰分离,提供⼀种创建对象的最佳⽅式。主要⽤于解决对象的构建过于复杂的问题

建造者模式主要基于四个核⼼类实现:

  • 抽象产品类:
  • 具体产品类:⼀个具体的产品对象类
  • 抽象Builder类:创建⼀个产品对象所需的各个部件的抽象接⼝
  • 具体产品的Builder类:实现抽象接⼝,构建各个部件
  • 指挥者Director类:统⼀组建过程,提供给调⽤者使⽤,通过指挥者来构造产品
#include<iostream>
#include<cstdarg>
#include<string>
#include<memory>

//抽象类
class Computer
{
public:
    using ptr = std::shared_ptr<Computer>; //using 关键字的用法,相当于prt等价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 { \n";
        computer += "\tboard = " + _board + ",\n";
        computer += "\tdisplay = " + _display + ",\n";
        computer += "\tOS = " + _os + ",\n } \n";
        return computer; 
    }

protected:
    std::string _board;
    std::string _display;
    std::string _os;
};

//具体的macbook类继承抽象父类
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 MacBookBuilder : public Builder
{
public:
    using prt = std::shared_ptr<MacBookBuilder>;
    MacBookBuilder(): _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;  //类型是std::shared_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 *builder = new MacBookBuilder(); // 先构造一个具体的电脑builder类
    std::unique_ptr<Director> dp(new Director(builder)); //构造一个指挥者类
    dp->construct("华硕主板", "飞利浦显示器");
    Computer::ptr computer = builder->build(); //将builder构建出来的电脑实例用一个对象指针来接受
    std::cout << computer->toString(); //打印出对应的电脑信息

    return 0;
}

4.7代理模式

代理模式指代理控制对其他对象的访问,也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤。代理模式的结构包括⼀个是真正的你要访问的对象(⽬标类)、⼀个是代理对象。⽬标对象与代理对象实现同⼀个接⼝,先访问代理类再通过代理类访问⽬标对象。代理模式分为静态代理、动态代理:

  • 静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
  • 动态代理指的是,在运⾏时才动态⽣成代理类,并将其与被代理类绑定。这意味着,在运⾏时才能
    确定代理类要代理的是哪个被代理类 。

以租房为例,房东将房⼦租出去,但是要租房⼦出去,需要发布招租启⽰,带⼈看房,负责维修,这些⼯作中有些操作并⾮房东能完成,因此房东为了图省事,将房⼦委托给中介进⾏租赁。代理模式实现。

//代理模式
class RentHouse
{
public:
    virtual void rentHouse() = 0; //纯虚函数子类必须实现
};

//中介
class Landlord : public RentHouse
{
public:
    void rentHouse()
    {
        std::cout << "将房子租出去\n";
    }
};

//中介代理类
class Intermediary : public RentHouse
{
public:
    void rentHouse()
    {
        std::cout << "发布招租启示\n";
        std::cout << "带人看房\n";
        _landlord.rentHouse();
        std::cout << "负责租后维修\n";
    }

private:
    Landlord _landlord;
};

int main()
{
    Intermediary intermediary;

    intermediary.rentHouse();

    return 0;
}

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1491732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows安装tdm-gcc(详细图文)

目录 一、tdm-gcc的下载地址 二、安装tdm-gcc 1.双击下载的tdm-gcc 2.点击create 下载 3.下载完成之后&#xff0c;点击第二个&#xff0c;再点击next。 4.更换安装目录 5. 全部勾选&#xff0c;点击install 三、配置系统环境变量 四、验证TDM-GCC 一、tdm-gcc的下载地…

O2O:Offline–Online Actor–Critic

IEEE TAI 2024 paper 1 Introduction 一篇offline to online 的文章&#xff0c;有效解决迁移过程出现的performance drop。所提出的O2AC算法首先在离线阶段添加一项BC惩罚项&#xff0c;用于限制策略靠近专家策略&#xff1b;而在在线微调阶段&#xff0c;通过动态调整BC的权…

鸿蒙全栈开发必学!码牛课堂《HarmonyOS NEXT星河版零基础入门到实战教程》,学到就是赚到!

众所周知&#xff0c;码牛发布的免费教程不仅质量高&#xff0c;而且更新快&#xff0c;帮助无数大学生成功踏入IT行业&#xff0c;被同学们亲切的称为“IT启蒙导师”。 今年被称为鸿蒙元年&#xff0c;各行业急缺鸿蒙相关人才&#xff0c;从招聘情况来看&#xff0c;鸿蒙人才…

低代码工具APEX的入门使用(未包含安装)

第一次使用APEX是2019年&#xff0c;这个技术成名已久只是我了解的比较晚。请看Oracle ACE的网站&#xff0c;这就是用APEX做的。实际上有一次我看O记的人操作他们的办公流程&#xff0c;都是用APEX做的。 那一年&#xff0c;我用APEX做了一个CMDB的管理系统。那时候还没有流行…

微信小程序开发学习笔记《19》uni-app框架-配置小程序分包与轮播图跳转

微信小程序开发学习笔记《19》uni-app框架-配置小程序分包与轮播图跳转 博主正在学习微信小程序开发&#xff0c;希望记录自己学习过程同时与广大网友共同学习讨论。建议仔细阅读uni-app对应官方文档 一、配置小程序分包 分包可以减少小程序首次启动时的加载时间 为此&#…

S/4 HANA CLOUD Workaround 销售含税价

业务场景&#xff1a; 在中国及其他亚洲或者东南亚国家&#xff0c;采购合同和发票都是以含税价的方式计价&#xff0c;这与美国及欧洲国家通过净价加税的计价方式不同。而目前S/4 HANA CLOUD交付的标准定价方式采取的是后者&#xff0c;大部分的中国客户对含税价的功能都有需求…

图书推荐|Word文稿之美

让你的文档从平凡到出众&#xff01; 本书内容 《Word文稿之美》是一本全面介绍Word排版技巧和应用的实用指南。从初步认识数字排版到高效利用模板、图文配置和表格与图表的排版技巧&#xff0c;再到快速修正错误和保护文件&#xff0c;全面系统地讲解数字排版的技术和能力&…

Git 撤销修改

如果我们在我们的工作区写了很长时间代码&#xff0c;发现出现错误&#xff0c;想回退到之前的版本&#xff0c;这时改怎么做呢&#xff1f; 情况一&#xff1a;对于工作区的代码&#xff0c;还没有 add 我们当然也可以使用git diff 查看与上次提交的差异&#xff0c;进行手动删…

【数据结构】用栈实现队列

前言&#xff1a;本节博客分享了用栈实现队列效果的思路以及代码&#xff0c;有需要借鉴即可。 1.题目及链接 LINK 2.思路分析 如果要用栈实现队列&#xff0c;我们直到栈是先入后出的一个效果&#xff0c;所以我们可以用两个栈&#xff0c;这样逆转两次数不就是入栈之前数组…

代码随想录算法训练营第十四天| 144. 二叉树的前序遍历 ,145. 二叉树的后序遍历,94. 二叉树的中序遍历

两种写法&#xff0c;递归和非递归写法 递归&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : va…

如何使用宝塔面板部署MySQL数据库,并结合内网穿透实现固定公网地址远程连接

文章目录 前言1.Mysql服务安装2.创建数据库3.安装cpolar3.1 开放局域网端口3.2 创建HTTP隧道 4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 宝塔面板的简易操作性,使得运维难度降低,简化了Linux命令行进行繁琐的配置,下面简单几…

VMvare17安装centos8安装宝塔面板 教程

阿里镜像站&#xff1a;https://mirrors.aliyun.com/centos centos-8-isos-x86_64安装包下载_开源镜像站-阿里云 https://mirrors.aliyun.com/centos/8/isos/x86_64/CentOS-8.5.2111-x86_64-dvd1.iso 将上面的链接复制到迅雷进行高速下载 vmvare安装配置教程安装教程 CentOS…

激光炸弹 刷题笔记

前置知识 二维前缀和 子矩阵的和 刷题笔记 {二维前缀和}-CSDN博客 思路 参考二维前缀和 将子矩阵的和 做成动态矩阵 一个个矩阵搜索 符合要求边长 矩阵中的元素和最大值 将x1,y1用i-k,j-k表示即可 x2,y2用i&#xff0c;j表示 代码 #include<iostream> #include<…

16:00面试,16:06就出来了,问的问题过于变态了。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到2月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

10.WEB渗透测试-Linux基础知识-Linux用户权限管理(下)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;9.WEB渗透测试-Linux基础知识-Linux用户权限管理&#xff08;上&#xff09;-CSDN博客 ch…

CSS元素分类,轻松拿下offer

戳这里领取完整开源项目&#xff1a;【一线大厂前端面试题解析核心总结学习笔记Web真实项目实战最新讲解视频】 面试题 HTML 1&#xff0c;html5有哪些新特性&#xff1f; 2&#xff0c;html5移除了那些元素&#xff1f; 3&#xff0c;如何处理HTML5新标签的浏览器兼容问题…

go 程序被意外kill后出现僵尸进程解决方案

go 管理自身子进程(防止僵尸进程出现) 写这篇文章是因为最近有同事竟然会知道异步启动子进程&#xff0c;不会关闭&#xff0c;最后导致导致僵尸进程出现&#xff0c;而且由于子进程会随着业务的使用越开越多&#xff0c;主进程一旦被kill掉就会不得不手动一个一个kill。 大概…

Java消息服务(JMS):在异步通信世界的引领者

文章目录 前言需求演进异步通信的需求增长面向消息的中间件兴起标准化的迫切需求 与相似框架的对比JMS vs AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;JMS vs MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;JMS vs Apache Kafka 完整的…

毫秒生成的时间戳如何转化成东八区具体时间

假设现在有一个时间是1709101071419L 后端代码实现 Java代码&#xff08;东八区时间&#xff09; 在Java代码中&#xff0c;我们将时区从UTC调整为东八区&#xff08;UTC8&#xff09;&#xff1a; import java.time.Instant; import java.time.ZoneId; import java.time.Z…

onnx runtime文档学习2-torch TF简单示例

网上充斥着ONNX Runtime的简单科普&#xff0c;却没有一个系统介绍ONNX Runtime的博客&#xff0c;因此本博客旨在基于官方文档进行翻译与进一步的解释。ONNX runtime的官方文档&#xff1a;https://onnxruntime.ai/docs/ 如果尚不熟悉ONNX格式&#xff0c;可以参照该博客专栏…