三步实现自动注册工厂替代switch语句(c++)

2024-04-20 11:58

本文主要是介绍三步实现自动注册工厂替代switch语句(c++),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

在学长博客里看到了使用自动注册工厂替代switch语句的文章,想到可以将其用到自己的项目里,就照猫画虎学习着也实现了一个。
这里并不是用其替代创建派生类的传统Factory,而是为了替代服务器中的业务逻辑处理。
记得以前实现的第一个服务器项目:聊天室,里面在解包后,是一长串的switch语句,根据包里类型标志,来决定该如何处理,写起来舒服,可看起来,包括后期维护,实在是太不方便,因为想使用自动注册工厂这种模式来解决这个问题。

我这里的自动注册工厂是针对服务器项目收到包之后进行逻辑处理的语句冗余问题。
但思路与代码基本适用于绝大多数需要使用自动注册工厂的情况。

以前的方式

switch(type)
{case 1:do_login();case 2:do_register();case 3:do_something();case ...........
}

显然,在业务逻辑并不多的时候,这样的方式也无伤大雅,那么假设业务逻辑很多呢,switch语句该有多长,无论是维护还是阅读都很不便。
那么,我们来看下一种方式。

自动注册工厂

逻辑处理基类

这里只简单的实现了基本的构造函数,和逻辑处理函数

class action
{
public:action(){std::cout<<"action"<<std::endl;}virtual void doAction(){std::cout<<"doAction"<<std::endl;}};
登陆逻辑处理派生类
class login_action : public action {
public:login_action(){std::cout<<"login_action"<<std::endl;}void doAction(){std::cout<<"do_login_action"<<std::endl;}
};REGISTER_ACTION(login_action, "login_action");
注册逻辑处理派生类
class register_action : public action
{
public:register_action(){std::cout<<"register_action"<<std::endl;}void doAction(){std::cout<<"do_register_action"<<std::endl;}
};REGISTER_ACTION(register_action, "register_action");
工厂类

这个类是我们自动注册工厂的核心类。

第一步

首先,我们要将其设计为单例模式,为了规范,我们将其拷贝构造函数和移动构造函数都设置为私有的,令其不可拷贝与构造,类似于boost::noncopyable。
并定义一个私有变量,为map类型,键为string,值为可返回一个派生类对象的function。
如下:

{
public:
private:factory() = default;factory(const factory&) = delete;factory(factory&&) = delete;static factory &get(){static factory instance;return instance;}std::map<std::string, std::function<action*(void)>> m_map;
};
第二步

在factory内实现一个内部类Register,便于扩展,我将其设置为了模板类型。
为什么要设置为内部类呢,因为设置为内部类我们就可以使用外部类的私有成员(我们为规范,将map设置为私有的),同时也因为其与工厂类本身就是一体,写在一起也更合逻辑。
构造函数:传入一个标志key,将其作为键写入map,值为一个lambda表达式,返回一个派生类对象指针。

    template <typename F>struct Register{Register(const std::string& key){factory::get().m_map.emplace(key, []{return new F();});}template<typename... Args>Register(const std::string& key, Args... args){factory::get().m_map.emplace(key, [&]{return new F(args...);});}};
注:emplace操作是C++11新特性,新引入的的三个成员emlace_front、empace 和 emplace_back,这些操作构造而不是拷贝元素到容器中,
这些操作分别对应push_front、insert 和push_back,允许我们将元素放在容器头部、一个指定的位置和容器尾部。
(目的是减少一次拷贝)
第三步

使用宏来简化工厂注册步骤

#define REGISTER_ACTION_NAME(T) msg_name_##T##_
#define REGISTER_ACTION(T, key, ...) \
static factory::Register<T> REGISTER_ACTION_NAME(T)(key,##__VA_ARGS__)
注:##起将左右字符衔接的作用__VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的。宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用
工厂类完整代码
class factory
{
public:template <typename F>struct Register{Register(const std::string& key){factory::get().m_map.emplace(key, []{return new F();});}template<typename... Args>Register(const std::string& key, Args... args){factory::get().m_map.emplace(key, [&]{return new F(args...);});}};static action* produce(const std::string& key){auto map = factory::get().m_map;if(map.find(key) == map.end()){throw std::invalid_argument("error");}return map[key]();}private:factory() = default;factory(const factory&) = delete;factory(factory&&) = delete;static factory &get(){static factory instance;return instance;}std::map<std::string, std::function<action*(void)>> m_map;
};#define REGISTER_ACTION_NAME(T) msg_name_##T##_
#define REGISTER_ACTION(T, key, ...) \
static factory::Register<T> REGISTER_ACTION_NAME(T)(key,##__VA_ARGS__)
使用
int main()
{action *login = factory::produce("login_action");action *rter = factory::produce("register_action");login->doAction();rter->doAction();delete(login);delete(rter);
}
输出

这里写图片描述

结语

我们在学习中可能业务逻辑并不会太多,也就是说,switch语句并不会影响什么,但我们应当在一开始时就养成这么一个好的习惯,用最好的方式去实现自己想要的功能。

这篇关于三步实现自动注册工厂替代switch语句(c++)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920204

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima