三步实现自动注册工厂替代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语句并不会影响什么,但我们应当在一开始时就养成这么一个好的习惯,用最好的方式去实现自己想要的功能。

请赐予我钱进的动力吧~
0%