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

news2024/9/21 14:47:58

使用对象的时候,可以直接 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://www.coloradmin.cn/o/1468540.html

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

相关文章

达尔克仪器设备邀您观摩2024生物发酵产品与技术装备展

参展企业介绍 达尔克本着“诚信、求精、协作、创新”的企业精神&#xff0c;以专业的技术、优良的品质和完善的服务&#xff0c;与广大客户共创辉煌未来。 我们的产品——包括水质分析、压力、温度、流量、物位等工业在线过程控制仪表、其他类型传感器以及自动化控制方案——全…

HTTPS原理及配置

HTTPS 1. 对称加密和非对称加密2. 加密算法&#xff08;了解&#xff09;3. HTTPS 协议介绍4. HTTPS 原理4.1. HTTP 访问过程4.2. HTTPS访问过程4.3. HTTPS 总结4.4. CA 证书认证机构 HTTPS 基本原理 HTTPS&#xff08;全称&#xff1a;HyperText Transfer Protocol over Secur…

模型 HBG(品牌增长)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。品牌增长法。 1 HBG(品牌增长)模型的应用 1.1 江小白使用HBG模型提高品牌知名度和销售额 选择受众市场&#xff1a;江小白的目标客户是年轻人&#xff0c;他们喜欢简单、时尚的产品。因此&#xff0c;江…

【最新Dubbo3深入理解】Dubbo3源码中的一些小技巧

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

如何用 Screen Studio 灵活高效录制可复用的教学视频?

如果你跟我一样&#xff0c;录制视频是高频刚需&#xff0c;那你不难体会到它对用户痛点的理解和解决。 挑战 作为一名教师兼 UP 主&#xff0c;我经常录制视频。 之前录制教程类视频&#xff0c;我往往采用自己的标准「懒人法」—— 从头到尾&#xff0c;用 OBS 录制屏幕和摄像…

R语言空间分析、模拟预测与可视化

随着地理信息系统&#xff08;GIS&#xff09;和大尺度研究的发展&#xff0c;空间数据的管理、统计与制图变得越来越重要。R语言在数据分析、挖掘和可视化中发挥着重要的作用&#xff0c;其中在空间分析方面扮演着重要角色&#xff0c;与空间相关的包的数量也达到130多个。在本…

Facebook群控:利用代理IP克服多账号关联

拥有多个 Facebook 帐户对于区分您的个人和企业在线形象或维护客户页面非常有用。然而&#xff0c;Facebook 的服务条款正式限制用户只能使用一个个人帐户&#xff0c;想要多账号运营&#xff0c;下面的干货必须看&#xff01; 一、Facebook群控是什么&#xff1f; Facebook群…

【安装记录】解决ssh密码正确,却无法连接到虚拟机

可能是没有允许Root登录 解决办法&#xff1a;修改/etc/ssh/sshd_config文件&#xff0c;将 PermitRootLogin 项打开

nginx 模块 常见内置变量 location

一、nginx 模块 ngx_http_core_module 核心模块 ngx_http_access_module 访问控制模块 deny allow ngx_http_auth_basic_module 身份验证 小红小名&#xff08;虚拟用户&#xff09; ftp也有虚拟用户 ngx_http_gzip_module 压缩模块 ngx_http_gzip_static_modul…

Day16_集合与泛型(泛型类与泛型接口,泛型方法,类型变量的上限与泛型的擦除,类型通配符)

文章目录 Day16 泛型学习目标1 泛型的概念1.1 没有泛型的问题1.2 泛型的引入1.2 泛型的好处1.3 泛型的定义 2 泛型类与泛型接口2.1 使用核心类库中的泛型类/接口案例一&#xff1a;Collection集合相关类型案例二&#xff1a;Comparable接口 2.2 自定义泛型类与泛型接口语法格式…

C语言中的字体背景颜色汇总

客官请看效果 客官请看代码 #include <stdio.h> #include <stdlib.h> #include <windows.h>int main() {int i;for (i 0; i < 254; i) {SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), i); // 设置当前文本颜色为循环变量对应的颜色printf(…

Maven - 代码混淆proguard-maven-plugin vs 代码加密classfinal

文章目录 proguard-maven-plugin 代码混淆官网地址入门小结 ClassFinal 代码加密介绍Gitee项目模块说明功能特性环境依赖使用说明下载加密maven插件方式无密码模式机器绑定启动加密后的jartomcat下运行加密后的war 版本说明协议声明 classfinal实战工程pom编译打包配置文件运行…

Dear ImGui的UE5.3集成实践

Dear ImGui一直较为火热&#xff0c;这是一个调试使用并且可以响应快速迭代的Gui库&#xff0c;甚至可以做到在任何代码块中调用API即显示。如果你想更多的了解一下可访问其官方网站&#xff1a;https://www.dearimgui.org/ 那么本文就来在UE5中尝试踩坑使用它。 UE4.26版本 …

LangChain Agent v0.2.0简明教程 (上)

快速入门指南 – LangChain中文网 langchain源码剖析系列课程 九天玩转Langchain! 1. LangChain是什么2. LangChain Expression Language (LCEL)Runnable 接口3. Model I/O3.1 Prompt Templates3.2 Language Model3.3 Output ParsersUse case(Q&A with RAG)1. LangChain…

手把手写深度学习(22):视频数据集清洗之过滤静态/运动程度低的数据

手把手写深度学习(0)&#xff1a;专栏文章导航 前言&#xff1a;当我们训练自己的视频生成模型时&#xff0c;现在大部分基于扩散模型架构都差不多&#xff0c;关键点在数据上&#xff01;视频数据的预处理远远比图像数据复杂&#xff0c;其中有一点是如果静态数据、运动程度低…

Python奇幻之旅(从入门到入狱高级篇)——面向对象进阶篇(下)

目录 引言 3. 面向对象高级和应用 3.1. 继承【补充】 3.1.1. mro和c3算法 c3算法 一句话搞定继承关系 3.1.2. py2和py3区别 3.3. 异常处理 3.3.1. 异常细分 3.3.2. 自定义异常&抛出异常 3.3.3. 特殊的finally 3.4. 反射 3.4.1. 一些皆对象 3.4.2. import_modu…

一元函数微分学——刷题(18

目录 1.题目&#xff1a;2.解题思路和步骤&#xff1a;3.总结&#xff1a;小结&#xff1a; 1.题目&#xff1a; 2.解题思路和步骤&#xff1a; 遇到绝对值函数&#xff0c;需要把它转化为分段函数&#xff0c;从而更加方便求导数&#xff1a; 3.总结&#xff1a; 遇到绝对…

算法沉淀——动态规划之斐波那契数列模型(leetcode真题剖析)

算法沉淀——动态规划之斐波那契数列模型 01.第 N 个泰波那契数02.三步问题03.使用最小花费爬楼梯04.解码方法 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种通过将原问题分解为相互重叠的子问题并仅仅解决每个子问题一次&#xff0c;将其解存…

Linux日志轮替

文章目录 1. 基本介绍2. 日志轮替文件命名3. logrotate 配置文件4. 把自己的日志加入日志轮替5. 日志轮替机制原理6. 查看内存日志 1. 基本介绍 日志轮替就是把旧的日志文件移动并改名&#xff0c;同时建立新的空日志文件&#xff0c;当旧日志文件超出保存的范围之后&#xff…

深度学习基础(四)医疗影像分析实战

之前的章节我们初步介绍了卷积神经网络&#xff08;CNN&#xff09;和循环神经网络&#xff08;RNN&#xff09;&#xff1a; 深度学习基础&#xff08;三&#xff09;循环神经网络&#xff08;RNN&#xff09;-CSDN博客文章浏览阅读1.2k次&#xff0c;点赞17次&#xff0c;收…