IOC容器实战

108 阅读3分钟

背景

以开发一个收发各类消息的APP为例。
我们会遇到,针对不同的消息类型,做出对应的处理的情况。
但是可能这个模块的架构并没有设计的很好,又随着功能的开发,经常会需要消新增消息类型。随着息类型的新增,这块消息处理的代码越来越复杂,比如一个文件上万行,最终难以扩展以及维护.

简单方案举例

最奔放的处理方式,代码难以阅读以及扩展

直接使用if else处理不同的消息类型

func recvMsg(msgType) {
    if (msgType == "Text") {
      //  block of code to be executed
    } else if (msgType == "Voice") {
      //  block of code to be executed
    } else {
      //  block of error handling code to be executed
    }
}

func sendMsg(msgType) {
    if (msgType == "Text") {
      //  block of code to be executed
    } else if (msgType == "Voice") {
      //  block of code to be executed
    } else {
      //  block of error handling code to be executed
    }
}

代码可读性强,但仍较难扩展以及维护

使用switch case处理不同的消息类型

func sendMsg(msgType) {
    switch(msgType) {
      case "Text":
        // block of code to be executed
        break;
      case "Voice":
        // block of code to be executed
        break;
      default:
        // block of error handling code to be executed
    }
}

func recvMsg(msgType) {
    switch(msgType) {
      case "Text":
        // block of code to be executed
        break;
      case "Voice":
        // block of code to be executed
        break;
      default:
        // block of error handling code to be executed
    }
}

IOC方案

好的设计会增加代码量,可能初看会觉得复杂,但是当你了解这个设计之后,又会觉得逻辑无比清晰,无论排查问题或者新增功能都得心应手。

方案对比

上述的简单方案,消息的收发模块与执行各个消息处理模块耦合在一起,导致消息模块的代码随着消息类型的增加,越来越庞大。
IOC方案将消息的收发模块与各个消息处理模块解耦,消息收发模块不再持有消息处理模块对象,转而持有IOC容器对象。

详细设计

类图

Msg Handler.svg

  • 主要逻辑
  1. 控制反转
    将MsgManager对各个MsgHandler的依赖,转化为对IOC容器的依赖,同时各个MsgHandler依赖IOC容器。MsgManager不需要包含MsgHandler_Text的头文件,同时MsgHandler_Text也不需要包含MsgManager的头文件。MsgManager主要工作就是分发消息到各个Handler,无论后续新增多少个Handler,MsgManager的实现都无需改动。
  2. 自注册
    MsgHandler_Text通过ObjectRegistrar在自己文件中实现在MsgHandlerContext中的注册。

代码介绍

核心容器代码

//  BaseFactory.h

template <typename T>
class HandlerContext {
    
public:
    HandlerContext(HandlerContext const&) = delete;
    HandlerContext(HandlerContext &&) = delete;
    
    HandlerContext& operator=(HandlerContext const&) = delete;
    HandlerContext& operator=(HandlerContext &&) = delete;
    
    T* getClass(const std::string& msgType) //按照消息类型获取Handler
    {
        auto iter = mHandlers.find(msgType);
        if (iter != mHandlers.end())
        {
            return iter->second;
        }
        return nullptr;
    }
    
    bool Register(std::string msgType, T* ptr) //按照消息类型注册Handler
    {
        if (ptr == nullptr)
        {
            return false;
        }
        
        auto ret_pair = mHandlers.insert(std::make_pair(msgType, ptr));
        return ret_pair.second;
    }
    
protected:
    HandlerContext() = default;
    ~HandlerContext() = default;
    
    std::unordered_map<std::string, T*> mHandlers;//通过保存自注册的处理对象,实现对象的随用随取
};

template <typename Context, typename T>
class ObjectRegistrar {
public:
    ObjectRegistrar(std::string msgType) { Context::shareInstance()->Register(msgType, T::shareInstance());
    }
};

业务代码举例


//  MsgHandlerContext.h
#define REGISTER_MSG_HANDLER(MsgType, Handler) \
static ObjectRegistrar<MsgHandlerContext, Handler> global_##MsgType##Registrar(#MsgType); //通过静态变量的构造方法实现自注册

// MsgManager.cpp
void MsgManager::recvMsg()
{
    auto* handlerContext = MsgHandlerContext::shareInstance();

    //根据消息类型直接分发到对应的Handler,不在需要复杂的if else逻辑或者switch case逻辑
    auto handler = handlerContext->getClass(msgType);
    if (handler)
    {
        auto sendMap = handler->recvMsg();
    }
}

//  MsgHandler_Text.cpp
REGISTER_MSG_HANDLER(Text, MsgHandler_Text) //注册当前类

MsgHandler_Text* MsgHandler_Text::shareInstance() {
    static MsgHandler_Text shareInstance;
    
    return &shareInstance;
}

void MsgHandler_Text::recvMsg() {
    // block of code to be executed
}

void MsgHandler_Text::sendMsg() {
    // block of code to be executed
}

优点

  1. 可读性强,新增消息类型,均在各个业务对应的文件中实现。
  2. 易扩展,新增消息类型,只需新增对应消息类型命名的文件即可。