IOC容器

  • Ioc容器是什么
  • IoC实现细节
  • IoC创建对象
  • 擦除类型常用的方法
  • 支持配置接口和实现的关系
  • 总结


Ioc容器是什么

是为了实现某种机制,让某一对象创建不再直接依赖于外部对象创建。外界通过需求灵活的配置这种机制创建对象,这种机制称为控制反转(Inversion of Control, IoC).
我们一般通过依赖注入,将对象创建的依赖关系注入目标对象的构造函数中!比如A对象依赖于B对象的关系注入到A类对象的构造函数中。

IoC实现细节

IoC实际上具有两种能力:

  1. 对象的工厂能力,可以创建所有的对象,还能根据配置去创建对象。
  2. 可以创建依赖对象,应用不需要直接创建依赖对象,有IoC容器创建,实现控制反转。

为了实现这个功能,我们需要解决几个问题:

  1. 创建所有类型的对象
  2. 类型擦除(Any)
  3. 创建依赖对象

IoC创建对象

#include <iostream>
#include <string>
#include <map>
#include <functional>
#include <memory>
using namespace std;

template<class T>
class IocContainer
{
public:
	using FuncType = std::function<T*()>;

	//注册一个key对应的类型
	template<class ClassType>
	void registerType(string key)
	{
		FuncType func = [] {return new ClassType(); };
		registerType(key, func);
	}

	//根据唯一标志查询对应的构造器 创建对象
	T* resolve(string key)
	{
		if (m_map.find(key) == m_map.end())
		{
			return nullptr;
		}
		auto func = m_map[key];
		return func();
	}
	std::shared_ptr<T> resolveShared(string key)
	{
		if (m_map.find(key) == m_map.end())
		{
			return nullptr;
		}
		auto func = m_map[key];
		return std::shared_ptr<T>(func());
	}

private:
	void registerType(string key, FuncType type)
	{
		if (m_map.find(key) != m_map.end())
		{
			throw std::invalid_argument("this key has exist");
		}
		m_map.emplace(key, type);
	}

private:
	std::map<string, FuncType> m_map;
};

struct ICar
{
	virtual ~ICar() {}
	virtual void test() const = 0;
};

struct Car : ICar
{
	void test() const
	{
		cout << "Car test" << endl;
	}
};

struct Bus : ICar
{
	void test() const
	{
		cout << "Bus test" << endl;
	}
};

int main()
{
	IocContainer<ICar> ioc;
	ioc.registerType<Bus>("bus");
	ioc.registerType<Car>("car");

	std::shared_ptr<ICar> bus = ioc.resolveShared("bus");
	bus->test();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             

	return 0;
}

以上虽然可以创建无参数的派生对象,但存在几个问题:

  1. 只能创建无参数对象
  2. 只能创建一种类型的对象

擦除类型常用的方法

  • 多态(基类类型约束)
  • 模板(缺点是容器的类型T必须要指定)
  • 类型容器(Variant可以承载指定的某些类型)
  • 通用类型擦除(Any类型可以满足使用)
  • 闭包 (例如使用lambda表达式封装起来)
    本次实现的Ioc容器主要使用Any和闭包擦除技巧实现。

####Any和闭包擦除类型
上面讲的Ioc容器必须要知道容器存储的数据类型,我们通过一个Any类型擦除这个限制。Any实现见:链接

class IocContainer
{
public:

	//注册一个key对应的类型
	template<class T, class Depend, class =
		std::enable_if<!std::is_same<Depend, void>::value>::type>
	void registerType(string key)
	{
		std::function<T*()> func = [] {return new T(new Depend()); };
		registerType(key, func);
	}

	//简单的对象工厂
	template<class T>
		void registerType(string key)
	{
		std::function<T*()> func = [] {return new T(); };
		registerType(key, func);
	}

	//根据唯一标志查询对应的构造器 创建对象
	template<class T>
	T* resolve(string key)
	{
		if (m_map.find(key) == m_map.end())
		{
			return nullptr;
		}
		Any func = m_map[key];

		//转换为合适的类型
		std::function<T*()> f = func.cast<std::function<T*()>>();
		return f();
	}

	template<class T>
	std::shared_ptr<T> resolveShared(string key)
	{
		T* t = resolve<T>(key);
		return std::shared_ptr<T>(t);
	}

private:
	void registerType(string key, Any func)
	{
		if (m_map.find(key) != m_map.end())
		{
			throw std::invalid_argument("this key has exist");
		}
		m_map.emplace(key, func);
	}

private:
	std::map<string, Any> m_map;
};

测试程序如下:

struct ICar
{
	virtual ~ICar() {}
	virtual void test() const {}
};

struct Car 
{
	void test() const
	{
		cout << "Car test" << endl;
	}
};

struct Bus
{
	void test() const
	{
		cout << "Bus test" << endl;
	}
};

struct Base
{
	virtual void func() {}
	virtual ~Base(){}
};

struct DerivedB : Base
{
	virtual void func() {
		cout << "derived b" << endl;
	}
};

struct DerivedA : Base
{
	virtual void func() {
		cout << "derived a" << endl;
	}
};

struct A
{
	A(Base* ptr) : m_ptr(ptr) {}

	virtual void func() {
		m_ptr->func();
	}

private:
	Base* m_ptr;
};


	IocContainer ioc;
	ioc.registerType<A, DerivedA>("A");
	ioc.registerType<A, DerivedB>("B");

	auto pa = ioc.resolveShared<A>("A");
	auto pb = ioc.resolveShared<A>("B");

	pa->func();
	pb->func();
	cout << "-------------------" << endl;

	ioc.registerType<Bus>("bus");
	ioc.registerType<Car>("car");
	auto pbus = ioc.resolveShared<Bus>("bus");
	auto pcar = ioc.resolveShared<Car>("car");
	pbus->test();
	pcar->test();

测试结果如下:

istoreos容器怎么用_C++

这里的关键就是使用了lambda表达式擦除具体类型,然后将其赋值给Any类型。

###IoC支持可变参数
上面实现的IoC容器,依赖的对象构造函数都是无参数的,下面我们需要增加入参测试。

class IocContainer
{
public:

	//注册一个key对应的类型
	template<class T, class Depend, typename ...Args>
		void registerType(string key)
	{
		std::function<T*(Args...)> func = [] (Args...args){return new T(new Depend(args...)); };
		registerType(key, func);
	}

	//根据唯一标志查询对应的构造器 创建对象
	template<class T, typename...Args>
	T* resolve(string key, Args...args)
	{
		if (m_map.find(key) == m_map.end())
		{
			return nullptr;
		}
		Any func = m_map[key];

		//转换为合适的类型
		std::function<T*(Args...)> f = func.cast<std::function<T*(Args...)>>();
		return f(args...);
	}

	template<class T, typename...Args>
	std::shared_ptr<T> resolveShared(string key, Args...args)
	{
		T* t = resolve<T>(key, args...);
		return std::shared_ptr<T>(t);
	}

private:
	void registerType(string key, Any func)
	{
		if (m_map.find(key) != m_map.end())
		{
			throw std::invalid_argument("this key has exist");
		}
		m_map.emplace(key, func);
	}

private:
	std::map<string, Any> m_map;
};

struct Base
{
	virtual void func() {}
	virtual ~Base() {}
};

struct Derived : Base
{
	Derived(int a, double d) :m_a(a), m_d(d) {}
	virtual void func() {
		cout << "derived :"<<m_a + m_d << endl;
	}
private:
	int m_a;
	double m_d;
};

struct A
{
	A(Base* ptr) : m_ptr(ptr) {}

	virtual void func() {
		m_ptr->func();
	}

private:
	Base* m_ptr;
};

//测试代码
	IocContainer ioc;
	ioc.registerType<A, Derived, int, double>("A");
	auto pa = ioc.resolveShared<A>("A", 1, 2.0);
	pa->func();

istoreos容器怎么用_C++_02

支持配置接口和实现的关系

我们需要支持将接口和派生类的关系进行配置,这样后面就可以根据参数配置接口和派生类的关系。
主要修改的点就是判断继承关系,如果是派生类则直接创建对象。

//T和Depend没有继承关系
	template<class T, class Depend, typename ...Args>
	typename std::enable_if<!std::is_base_of<T, Depend>::value>::type //此时type为void
	registerType(string key)
	{
		std::function<T*(Args...)> func = [] (Args...args){return new T(new Depend(args...)); };
		registerType(key, func);
	}
	//T和Depend有继承关系
	template<class T, class Depend, typename ...Args>
	typename std::enable_if<std::is_base_of<T, Depend>::value>::type //此时type为void
		registerType(string key)
	{
		std::function<T*(Args...)> func = [](Args...args) {return new Depend(args...); };
		registerType(key, func);
	}
struct Interface
{
	virtual void func() {}
	virtual ~Interface() {}
};

struct Derived : Interface
{
	Derived(int a, double d) :m_a(a), m_d(d) {}
	virtual void func() {
		cout << "derived :"<<m_a + m_d << endl;
	}
private:
	int m_a;
	double m_d;
};

//测试代码
	IocContainer ioc;
	ioc.registerType<Interface, Derived, int, double>("interface");
	auto pa = ioc.resolveShared<Interface>("interface", 1, 2.0);
	pa->func();

总结

通过依赖注入的方式实现控制反转,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定。
由容器动态的将某种依赖关系注入到组件中,方式有很多种:C#和Java中支持构造函数、属性和方法的注入。由于C++不支持反射和标签,不能实现属性和方法的调用和注入,只能实现构造函数的依赖注入。

我们上面对象成功注册到对了Ioc容器,并且通过关键字关联,随后通过配置文件可以很容易实现上面的效果。