C++ 中的微型 IOC 容器





5.00/5 (1投票)
微型 IOC 容器
引言
控制反转容器允许系统实现依赖注入,通过间接指定对象的依赖关系。通常,IOC 容器将负责实际实例化具体的对象,选择构造函数并仅返回引用类型,在调用构造函数之前获取任何依赖项的实例。今天,我尝试创建这样一个最小的容器,并想与大家分享。
获取类型代码
在我们可以安全地存储一种获取订阅特定接口的类的方法之前,我们应该为此分配一个类型代码。在我的 IOC 容器实现中,我有一个静态整数变量,它指定将要分配的下一个类型 ID,以及每个类型的一个静态局部变量实例,可以通过调用 GetTypeID
方法来访问。
class IOCContainer
{
static int s_nextTypeId;
public:
//one typeid per type
template<typename T>
static int GetTypeID()
{
static int typeId = s_nextTypeId ++;
return typeId;
}
获取对象实例
好的,这很简单。现在既然我们有了类型 ID,我们应该能够存储某种工厂对象来表示我们不知道如何创建对象的事实。由于我想将所有工厂存储在同一个集合中,我选择了一个抽象基类,工厂将从中派生,以及一个捕获稍后调用的函数对象的实现。
为了简洁起见,我使用 std::map
来保存工厂,但是为了提高效率,你可能需要考虑其他选项。如果你一次构建集合然后多次使用它,那么一个排序的 std::vector
的 std::pair
会更有意义。
class FactoryRoot
{
public:
virtual ~FactoryRoot() {}
};
//todo: consider sorted vector
std::map<int, std::shared_ptr<FactoryRoot>> m_factories;
template<typename T>
class CFactory: public FactoryRoot
{
std::function<std::shared_ptr<T> ()> m_functor;
public:
~CFactory() {}
CFactory(std::function<std::shared_ptr<T> ()> functor)
:m_functor(functor)
{
}
std::shared_ptr<T> GetObject()
{
return m_functor();
}
};
template<typename T>
std::shared_ptr<T> GetObject()
{
auto typeId = GetTypeID<T>();
auto factoryBase = m_factories[typeId];
auto factory = std::static_pointer_cast<CFactory<T>>(factoryBase);
return factory->GetObject();
}
注册实例
现在我们只需要填充集合了。我已经实现了几种不同的方法来做到这一点:你可能想显式地提供一个函数对象,或者你可能想在单个实例或按需创建新实例之间进行选择。
//Most basic implementation - register a functor
template<typename TInterface, typename ...TS>
void RegisterFunctor(std::function<std::shared_ptr<TInterface>
(std::shared_ptr<TS> ...ts)> functor)
{
m_factories[GetTypeID<TInterface>()] =
std::make_shared<CFactory<TInterface>>([=]{return functor(GetObject<TS>()...);});
}
//Register one instance of an object
template<typename TInterface>
void RegisterInstance(std::shared_ptr<TInterface> t)
{
m_factories[GetTypeID<TInterface>()] =
std::make_shared<CFactory<TInterface>>([=]{return t;});
}
//Supply a function pointer
template<typename TInterface, typename ...TS>
void RegisterFunctor(std::shared_ptr<TInterface> (*functor)(std::shared_ptr<TS> ...ts))
{
RegisterFunctor(std::function<std::shared_ptr<TInterface>
(std::shared_ptr<TS> ...ts)>(functor));
}
//A factory that will call the constructor, per instance required
template<typename TInterface, typename TConcrete, typename ...TArguments>
void RegisterFactory()
{
RegisterFunctor(
std::function<std::shared_ptr<TInterface> (std::shared_ptr<TArguments> ...ts)>(
[](std::shared_ptr<TArguments>...arguments) -> std::shared_ptr<TInterface>
{
return std::make_shared<TConcrete>
(std::forward<std::shared_ptr<TArguments>>(arguments)...);
}));
}
//A factory that will return one instance for every request
template<typename TInterface, typename TConcrete, typename ...TArguments>
void RegisterInstance()
{
RegisterInstance<TInterface>(std::make_shared<TConcrete>(GetObject<TArguments>()...));
}
};
使用场景
好的,我们差不多准备好了。在这里,我将展示一个玩具使用场景,我注册了两个对象——一个带有将被调用并传入另一个类型实例的构造函数。你会注意到我必须在这里定义静态类型 ID 计数器,并赋予它一个初始值——这里使用的值并不重要,但我只是希望它是一个容易识别的值。
IOCContainer gContainer;
//initialise with nonzero number
int IOCContainer::s_nextTypeId = 115094801;
class IAmAThing
{
public:
virtual ~IAmAThing() { }
virtual void TestThis() = 0;
};
class IAmTheOtherThing
{
public:
virtual ~IAmTheOtherThing() { }
virtual void TheOtherTest() = 0;
};
class TheThing: public IAmAThing
{
public:
TheThing()
{
}
void TestThis()
{
std::cout << "A Thing" << std::endl;
}
};
class TheOtherThing: public IAmTheOtherThing
{
std::shared_ptr<IAmAThing> m_thing;
public:
TheOtherThing(std::shared_ptr<IAmAThing> thing):m_thing(thing)
{
}
void TheOtherTest()
{
m_thing->TestThis();
}
};
int main(int argc, const char * argv[])
{
gContainer.RegisterInstance<IAmAThing, TheThing>();
gContainer.RegisterFactory<IAmTheOtherThing, TheOtherThing, IAmAThing>();
gContainer.GetObject<IAmTheOtherThing>()->TheOtherTest();
return 0;
}
最后一点
差不多就是这次的内容了。我认为你可以做一些事情来改进容器,但这只是一个开始。如果你有任何评论、建议或投诉,请告诉我!我很乐意收到你的反馈。感谢阅读!
历史
- 2015 年 9 月 13 日:初始版本
- 2021 年 3 月 6 日:初始版本