[c++] 工厂模式 + cyberrt 组件加载器分析

使用对象的时候,可以直接 new 一个,为什么还需要工厂模式 ?

工厂模式属于创建型设计模式,将对象的创建和使用进行解耦,对用户隐藏了创建逻辑。

个人感觉上边的表述并没有说清楚为什么需要使用工厂模式。因为使用 new 创建一个对象的时候,比如 new Object(x, x, x, x),对象创建逻辑在构造函数中实现,逻辑也对用户隐藏了,在一定程度上也实现了解耦。

为什么使用工厂模式 ?

生活中的工厂是生产商品的,一个工厂生产的商品往往有多个种类,比如手机代工厂,可能会代工多个品牌的手机。c++ 的工厂模式来源于生活,工厂模式常常应用于多态的场景,有一个基类,派生出多个类,创建这些类的时候,使用工厂模式就比较合适。

比如一个工厂代工了 3 个品牌的手机,apple, mi, oppo。如下图所示,我们往往会定义一个基类 Phone,在基类的基础上派生出 3 个子类:ApplePhone,MiPhone,OppoPhone。如果开发者在创建这些类的时候,需要分别 new ApplePhone(),new MiPhnoe(),new OppoPhone(),这样使用 new 的方式就不如使用工厂模式简单了。

1 简单工厂

手机工厂的例子,代码如下。

(1)一个抽象类,class Phone

(2)3 个派生类,ApplePhone,MiPhone, OppoPhone

(3)一个工厂类 PhoneFactory

可以看到,相对于直接使用 new 来创建对象,工厂类就是把产品的类型和类的对应关系这个逻辑给隐藏起来了,用户使用的时候只需要传一个手机类型就可以了。

#include <iostream>
#include <string>class Phone {
public:virtual ~Phone() {}virtual void CallUp() = 0;
};class ApplePhone : public Phone {
public:void CallUp() {std::cout << "ApplePhone call up" << std::endl;}
};class MiPhone : public Phone {
public:void CallUp() {std::cout << "MiPhone call up" << std::endl;}
};class OppoPhone : public Phone {
public:void CallUp() {std::cout << "OppoPhone call up" << std::endl;}
};enum PHONE_TYPE {APPLE,MI,OPPO
};class PhoneFactory {
public:Phone *CreatePhone(PHONE_TYPE type) {switch (type) {case APPLE:return new ApplePhone();case MI:return new MiPhone();case OPPO:return new OppoPhone();default:return nullptr;}}
};int main() {PhoneFactory phone_factory;Phone *apple = phone_factory.CreatePhone(APPLE);if (apple != nullptr) {apple->CallUp();delete apple;apple = nullptr;}Phone *mi = phone_factory.CreatePhone(MI);if (mi != nullptr) {mi->CallUp();delete mi;mi = nullptr;}Phone *oppo = phone_factory.CreatePhone(OPPO);if (oppo != nullptr) {oppo->CallUp();delete oppo;oppo = nullptr;}return 0;
}

2 工厂方法

简单工厂模式,如果生产的产品类型发生变化的时候需要改变工厂类,增减一个 if 分支,或者增减一个 case 分支 。有一个编码原则是 "对修改关闭,对扩展开发",简单工厂就违反了这条编码原则。因此,从简单工厂模式又延伸出了工厂方法模式。

工厂方法模式,需要增加一个工厂类。一个抽象工厂类 Factory,这个类派生出 3 个子类, AppleFactory,MiFactory,OppoFactory。

一个全局的表 map,这个 map 的 key 是商品的类型,value 是工厂类。这样的话,如果需要增加一种商品,只需要增加一个工厂类,然后在 map 中增加一项就可以了。

工厂类创建对象的时候,根据类型去 map 中查找对应的工厂,找到工厂之后就行生产。这样当增加商品的时候就彻底不需要修改工厂类了。把修改的范围缩小了,并且做了解耦。

多种设计模式中都使用了 map 来对代码进行解耦,并且做到对修改关闭,对扩展开方。比如策略模式,职责链模式。 

代码如下,代码中有一个全区的数据结构 factory_map,key 是手机类型,value 是工厂类。每定义一个工厂类,都可以通过宏 REGISTER_FACTORY 将工厂类注册到 factory_map 中。这样在使用的时候,只需要根据类型去 factory_map 中查找对应的工厂类,找到之后就可以生产。

#include <iostream>
#include <string>
#include <map>enum PHONE_TYPE {APPLE,MI,OPPO
};class Factory;
std::map<PHONE_TYPE, Factory *> factory_map;#define REGISTER_FACTORY(factory_type, class_name) \
struct ClassRegister##class_name { \ClassRegister##class_name() { \factory_map[factory_type] = new class_name(); \}; \
}; \
static struct ClassRegister##class_name factory_register##class_name;// 手机类
class Phone {
public:virtual ~Phone() {}virtual void CallUp() = 0;
};class ApplePhone : public Phone {
public:void CallUp() {std::cout << "ApplePhone call up" << std::endl;}
};class MiPhone : public Phone {
public:void CallUp() {std::cout << "MiPhone call up" << std::endl;}
};class OppoPhone : public Phone {
public:void CallUp() {std::cout << "OppoPhone call up" << std::endl;}
};// 工厂类
class Factory {
public:virtual ~Factory() {};virtual Phone *CreatePhone() = 0;
};class AppleFactory : public Factory {
public:Phone *CreatePhone() {return new ApplePhone();};
};
REGISTER_FACTORY(APPLE, AppleFactory);class MiFactory : public Factory {
public:Phone *CreatePhone() {return new MiPhone();};
};
REGISTER_FACTORY(MI, MiFactory);class OppoFactory : public Factory {
public:Phone *CreatePhone() {return new OppoPhone();};
};
REGISTER_FACTORY(OPPO, OppoFactory);class PhoneFactory {
public:Phone *CreatePhone(PHONE_TYPE type) {if (factory_map.count(type) > 0) {return factory_map[type]->CreatePhone();}return nullptr;}
};int main() {PhoneFactory phone_factory;Phone *apple = phone_factory.CreatePhone(APPLE);if (apple != nullptr) {apple->CallUp();delete apple;apple = nullptr;} else {std::cout << "apple is nulptr" << std::endl;}Phone *mi = phone_factory.CreatePhone(MI);if (mi != nullptr) {mi->CallUp();delete mi;mi = nullptr;}Phone *oppo = phone_factory.CreatePhone(OPPO);if (oppo != nullptr) {oppo->CallUp();delete oppo;oppo = nullptr;}return 0;
}

3 cyberrt 中组件加载器

cyberrt 中提供了两个基类 TimerComponent 和 Component。用户可以根据自己的业务需求,是定时触发还是事件触发来决定基于 TimerComponent 开发还是基于 Component 开发。比如自动驾驶系统中的传感器组件(camera,lidar,radar) 一般是定时组件,定时将传感器数据向外发布;感知,预测,决策,控制模块一般是事件触发,一般基于 Component 开发。如下图所示,TimerComponent 和 Component 由 ComponentBase 派生出来,这 3 个类都是属于架构层的类;上层的 camera,lidar,radar,perception,prediction 等组件属于业务层。

用户开发的软件最后会编译成一个动态库,动态库的加载和运行通过 cyberrt 中的组件加载器来进行。组件加载器的实现使用了工厂模式,并且使用的是工厂方法模式。

3.1 类加载

cyberrt 加载运行组件的时候,首先要加载用户的动态库。动态库的加载通过类 ClassLoader 来完成。

(1)动态库加载函数 dlopen()

底层动态库的加载是通过函数 dlopen() 完成。dlopen() 可以直接传动态库的名字,比如 libcamera.so,也可以传动态库的路径,比如 /ads/lib/libcamera.so。传动态库名字的时候,dlopen() 查找动态库的时候会根据系统的配置来查找,查找的路径有以下 5 个,按优先级先后顺序是 rpath > LD_LIBRARY_PATH > /etc/ls.so.cache > /lib > /usr/lib。

① rpath

在编译的时候可以加编译选项,比如  -Wl,-rpath,/ads/lib,这样 /ads/lib 的路径就会保存到动态库中。

② LD_LIBRARY_PATH

环境变量,可以配置动态库的路径。

③ /etc/ls.so.cache

这个缓存中的路径,可以通过 ldconfig -p 查看缓存中的动态库。

如果想要向这个缓冲区中配置路径,可以增加一个 conf 文件放到目录 /etc/ld.so.conf.d/ 中,然后再执行 ldconfig,就能通过 ldconfig -p 查看到自己的路径。

(2)保存动态库的全局数据结构

动态库加载之后要保存到一个数据结构中,数据结构是一个 vector,vector 中的元素是 std::pair 数据,pair 的 key 是动态库的路径,value 是表示加载动态库的类 SharedLibrary。

// using LibPathSharedLibVector =
//    std::vector<std::pair<std::string, SharedLibraryPtr>>;
LibPathSharedLibVector& GetLibPathSharedLibVector() {static LibPathSharedLibVector instance;return instance;
}

3.2 创建对象

(1)工厂类

用户开发的组件都要使用下边这个宏进行注册, name 是用户类的类名。在这个宏中最后会通过 RegisterClass() 创建一个工厂类,工厂类放到一个全局单例的 map 里,key 是 classname, value 是工厂类。

#define CYBER_REGISTER_COMPONENT(name) \CLASS_LOADER_REGISTER_CLASS(name, apollo::cyber::ComponentBase)

创建对象的时候是在函数 CreateObj() 中完成,对于没有形参的构造函数,使用 new 创建对象的时候,类名后边也可以不加括号。

template <typename ClassObject, typename Base>
class ClassFactory : public AbstractClassFactory<Base> {public:ClassFactory(const std::string& class_name,const std::string& base_class_name): AbstractClassFactory<Base>(class_name, base_class_name) {}Base* CreateObj() const { return new ClassObject; }
};

(2)工厂类创建

工厂类创建的在宏 CLASS_LOADER_REGISTER_CLASS_INTERNAL 中实现的。第 2 节工厂方法模式中,自己写的代码就是参考这个宏实现的。这里边有一个技巧,就是定义一个结构体,然后声明一个静态的结构体对象,这样就会调用结构体的构造函数,在构造函数中完成工厂类的注册。

#define CLASS_LOADER_REGISTER_CLASS_INTERNAL(Derived, Base, UniqueID)    \namespace {                                                            \struct ProxyType##UniqueID {                                           \ProxyType##UniqueID() {                                              \vcl::class_loader::utility::RegisterClass<Derived, Base>(#Derived, \#Base);   \}                                                                    \};                                                                     \static ProxyType##UniqueID g_register_class_##UniqueID;                \}

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

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

相关文章

12个的无时间限制的录屏软件详细比较

您可能尝试过许多录制程序&#xff0c;但大多数都会在30 分钟后停止录制萤幕。如果您需要录制较长的内容&#xff0c;特别是为公司会议或简报进行录制&#xff0c;您将必须找到最好的没有时间限制的录屏软件。这款录音软体可以让您长时间录音而没有任何麻烦。下面列出了12 款无…

亚马逊产品数据抓取

抓取数据 启动抓取 &#xff0c;亚马逊平台前台网站中可以查看、抓取、分析的一系列数据源&#xff0c;其数据种类繁多&#xff0c;本系统主要抓取产品列表&#xff08;包含主图、标题、价格、review分值、prime服务信息等&#xff09;、Listing详情信息&#xff08;包含5点描…

MyBatis---初阶

一、MyBatis作用 是一种更简单的操作和读取数据库的工具。 二、MyBatis准备工作 1、引入依赖 2、配置Mybatis(数据库连接信息) 3、定义接口 Mapper注解是MyBatis中用来标识接口为Mapper接口的注解。在MyBatis中&#xff0c;Mapper接口是用来定义SQL映射的接口&#xff0c;通…

超68万售出,sedo域名登顶最新一期交易排行榜

.com三字母域名售价超过68万人民币&#xff0c;币圈对应的四字母域名近期被曝光售价超过68万人民币。 近日&#xff0c;sedo平台交易信息显示&#xff0c;一个三字母域名被拍卖出10.5万美元&#xff0c;折合人民币超过68万人民币。 据查询&#xff0c;其注册时间为1995年&…

【FreeRTOS】任务创建

参考博客&#xff1a; ESP-IDF FreeRTOS 任务创建分析 - [Genius] - 博客园 (cnblogs.com) 1.什么是任务 1&#xff09;独立的无法返回的函数称为任务 2&#xff09;任务是无线循环 3&#xff09;无返回数据 2.任务的实现过程 1.定义任务栈 裸机程序&#xff1a;统一分配到一…

五种多目标优化算法(MOFA、NSWOA、MOJS、MOAHA、MOPSO)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 多目标优化算法是用于解决具有多个目标函数的优化问题的一类算法。其求解流程通常包括以下几个步骤&#xff1a; 1. 定义问题&#xff1a;首先需要明确问题的目标函数和约束条件。多目标优化问题通常涉及多个目标函数&#xff0c;这些目标函数可能…

软考44-上午题-【数据库】-数据定义语言DDL

一、SQL server数据库的体系结构 SQL server数据库的体系结构是由视图、基本表、存储文件&#xff0c;三级结构组成。 【回顾】&#xff1a;数据库的三级模式结构 视图&#xff1a;外模式 存储文件&#xff1a;内模式 基本表&#xff1a;概念模式 二、SQL语言的分类 SQL语言按…

Yolo v9 “Silence”模块结构及作用!

论文链接&#xff1a;&#x1f47f; YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information 代码链接&#xff1a;&#x1f47f; https://github.com/WongKinYiu/yolov9/tree/main Silence代码 class Silence(nn.Module):def __init__(self):supe…

2024最新零基础入门|白帽黑客学习教程,从0到黑客高手!

新手如何通过自学黑客技术成为厉害的白帽黑客&#xff1f; 我目前虽然算不上顶尖的白帽大佬&#xff0c;但自己在补天挖漏洞也能搞个1万多块钱。 给大家分享一下我的学习方法&#xff0c;0基础也能上手学习,如果你能坚持学完&#xff0c;你也能成为厉害的白帽子&#xff01; …

Escalate_Linux(4)-利用SUDO实现提权

利用SUDO实现提权 利用用户的sudo授权获得root的shell cat /etc/passwd cat /etc/sudoers 命令没有权限 echo "cat /etc/sudoers" >/tmp/ls chmod 755 /tmp/ls export PATH/tmp:$PATH /home/user5/script 想办法更改user1的口令 echo echo "user1:xiao…

【电子通识】为什么单片机芯片上会有多组VDD电源?

在单片机芯片规格书中&#xff0c;我们经常能看到多个组VDD的设计&#xff0c;如下红框所示管脚都是VDD管脚。 为什么需要这样设计&#xff1f;只设置一个VDD管脚&#xff0c;把其他的VDD管脚让出来多做几个IO或是其他复用功能不好吗&#xff1f;接下来我们从单片机内部的电路结…

ubuntu 22.04LTS的一些使用心得

前言 笔者一直在折腾ubuntu作为开发的主力系统&#xff0c;尤其是最近微信和各种软件陆续支持Debian系列&#xff0c;很多软件都可以用了&#xff0c;当然开源的软件大部分是跨平台的&#xff0c;尤其是idea系列。 X11 OR Wayland 关于X11和wayland&#xff0c;笔者还是使用…

CSRF靶场实战

DVWA靶场链接&#xff1a;https://pan.baidu.com/s/1eUlPyB-gjiZwI0wsNW_Vkw?pwd0b52 提取码&#xff1a;0b52 DVWA Low 级别打开靶场&#xff0c;修改密码 复制上面的 url&#xff0c;写个简单的 html 文件 <html <body> <a hrefhttp://127.0.0.1/DVWA/vulne…

leetcode hot100 买卖股票最佳时机3

本题中&#xff0c;依旧可以采用动态规划来进行解决&#xff0c;之前的两个题我们都是用二维数组dp[i][2]来表示的&#xff0c;其中i表示第i天&#xff0c;2表示长度为2&#xff0c;其中0表示不持有&#xff0c;1表示持有。 本题中&#xff0c;说至多完成两笔交易&#xff0c;也…

Linux系统添加新的网卡,并启用

在Rocky Linux系统中添加新的网卡并启用&#xff0c;一般涉及到以下步骤&#xff1a; 物理连接网卡&#xff1a; 首先确保你的虚拟机已经正确连接了新的网络适配器。 查看新添加的网卡&#xff1a; 在终端中输入以下命令来列出所有已识别的网络接口&#xff1a; ip link show …

基础复习(GDB调试)

1.GDB环境配置 这里只说Pwndbg Pwndbg的安装简单&#xff0c;访问Github主页&#xff0c;https://github.com/pwndbg/pwndbg 在“How”栏中看到安装说明 2.打开文件 GDB打开文件的方式与图形化的工具不同&#xff0c;需要通过传入参数或执行命令 方式1&#xff1a;在GDB的…

Matlab: Introduction to Hybrid Beamforming

文章目录 来源混合波束赋形的基本概念System Setup 来源 在matlab的命令行输入 doc hybrid beamforming 混合波束赋形的基本概念 混合波束形成简介 本例介绍了混合波束形成的基本概念&#xff0c;并说明了如何模拟这种系统。 现代无线通信系统使用空间复用来提高散射体丰富…

【ubuntu】永久修改主机名

文章目录 1. 问题描述2. 解决方案 1. 问题描述 主机名过长&#xff08;后面的部分&#xff09; 2. 解决方案 查看主机名详情 hostnamectl修改指定主机名 hostnamectl set-hostname ubuntu2204 --static登出重进即可

通过底层原理理解Java是值传递还是引用传递?

本文学习目标或者巩固的知识点 参数传递方式 值传递引用传递指针传递 彻底理解Java的值传递和引用传递 从底层的角度分析值传递会发生复制行为 Java的参数传递例子 快手的一面面试曾经问到过此类题目&#xff0c;所以记下此篇加深印象。 问&#xff1a;求下面main方法中的输…

深度学习500问——Chapter01:数学基础

文章目录 前言 1.1 向量和矩阵 1.1.1 标量、向量、矩阵、张量之间的联系 1.1.2 张量与矩阵的区别 1.1.3 矩阵和向量相乘结果 1.1.4 向量和矩阵的范数归纳 1.1.5 如何判断一个矩阵为正定 1.2 导数和偏导数 1.2.1 导数偏导计算 1.2.2 导数和偏导数有什么区别 1.3 特征值和特征向量…