C++11之如何實現控制反轉
一個小例子
我們先寫一個不使用控制反轉的小例子:
#include <iostream>
using namespace std;
struct A{
virtual void func(){}
virtual ~A(){}
};
struct B: public A{
func(){cout << "B" << endl;}
};
struct C:public A{
func(){cout << "C" << endl;}
}
class D{
public:
D(A* a):m_a(a){}
void func(){m_a->func();}
~D(){
if (m_a != nullptr)
{
delete m_a;
m_a = nullptr;
}
}
private:
A* m_a;
};
int _tmain(int argc, _TCHAR* argv[])
{
D* active = nullptr;
if (condtionB)
{
active = new D(new B());
}
else
{
active = new D(new C());
}
delete active;
return 0;
}
從上面的例子中可以看到D物件和A物件之間的耦合性就產生了。當A物件派生多個子類時,建立D物件時就不得不多加一個條件的判斷,這嚴重影響了程式碼的擴充套件性。也違背了“開放-封閉”的原則。耦合性產生的原因在於D物件的建立直接依賴於new外部物件,也叫做“硬編碼”,是的二者關係緊耦合,失去了靈活性。一種解決辦法是通過工廠模式來建立物件。
struct Factory{
static A * Create(const string & condition)
{
if (conditionB)
{
return new B();
}
else
{
return new C();
}
}
};
int _tmain(int argc, _TCHAR* argv[])
{
string condition = "B"
D* active = new A(Factory::Create(condition))
active->func();
delete active;
return 0;
}
這樣改進是不是感覺,以後A增加派生類只需要修改Factory的程式碼就可以了,而不是每次new 的時候都要寫好多判斷語句。但是這個方法還是沒有徹底將兩個物件之間的關係解耦。
要想徹底將兩個物件解耦就要引入一種機制,讓A物件不再直接依賴於外部物件的建立,而是以來某種機制,這種機制可以讓物件之間的關係在外面組裝,外界可以根據需求靈活的配置這種機制對的物件建立策略,而這種機制就是控制反轉(Ioc)。
控制反轉
控制反轉就是應用本身不負責依賴物件的建立和維護,而是交給外部容器來負責。這樣控制權就應由應用賺到了外部的Ioc.從而實現控制反轉。Ioc用來降低物件之間直接依賴產生的耦合性。
從上面的例子中,我們可以看到直接依賴會產生耦合性。
```
void IocSample{
IocContainer ioc;
ioc.RegisterType<D,A>("B");
ioc.RegisterType<D,A>("C");
//由Ioc容器去初始化D物件
D* active = ioc.Resolve<D>("B");
active->func();
delete active;
};
從上面的例子中,我們通過Ioc配置了D和A物件的關係,然後由Ioc容器去建立A物件,這裡A物件的建立不再依賴於工廠或者A物件,徹底解耦了而知之間的關係。
Ioc讓我們在建立物件上獲得了最大的靈活性,大大降低了依賴物件建立時的耦合性,及時需求變化了,也只需要修改配置檔案就可以建立想要的物件,而不是需要修改程式碼了。通過依賴注入(DI)將建立物件的依賴關係注入到目標型別的建構函式中。就像上面的例子將A依賴於B的依賴關係注入到A型別的建構函式中。
Ioc有兩種能力,一種是物件工廠的能力,不僅可以建立所有的物件,還能根據配置去建立物件;另一種能力是可以去建立依賴物件,應用部需要直接建立以來物件,由Ioc容器去建立實現控制反轉。
簡單Ioc的實現
void IocSample{
IocContainer ioc;
ioc.RegisterType<D,A>("B");
ioc.RegisterType<D,A>("C");
//由Ioc容器去初始化D物件
D* active = ioc.Resolve<D>("B");
active->func();
delete active;
};
#include <string>
#include <map>
#include <memory>
#include <functional>
using namespace std;
template <class T>
class IocContainer{
public:
IocContainer(){}
~IocContainer(){}
//註冊需要建立物件的建構函式,需要傳入一個唯一的標識
template<class Drived>
void RegisterType(string key){
std::function<T* ()> function = []{return new Drived();};//lambda表示式
RegisterType(key, function);
}
//根據唯一的標識去查詢對應的構造器,並建立指標物件
T* Resolve(string key){
if (m_creator.find(key) == m_creatoe.end())
{
return nullptr;
}
std::function<T*()> function = m_creator[key];
return function;
}
std::shared_ptr<T> ResolveShared(string key){
T* ptr = Resolve(key);
return std::shared_ptr<T>(ptr);
}
private:
void RegisterType(string key, std::function<T*()> creator){
if(m_creator.find(key) != m_creator.end){
throw std::invalid_argument("this key has already exist!");
}
m_creator.emplace(key,creator);
}
map<string,std::function<T*()>> m_creator;
};
測試一下
int _tmain(int argc, _TCHAR* argv[])
{
IocContainer<A> ioc;
ioc.RegisterType<A>("B");
ioc.RegisterType<A>("C");
std::function<A*()> deriveB = ioc.ResolveShared("B");
deriveB->func();
std::function<A*()> deriveC = ioc.ResolveShared("C");
deriveC->func();
return 0;
}
Result:
B
C
相信大家也注意到一個問題,上面的例子只能建立無參物件,還有一個問題就是隻能建立一種介面型別的物件,不能建立所有型別的物件。下面我們來看看怎麼解決這倆個問題。
Note:型別擦除常用的方法
1)通過多臺來擦除型別
2)通過模板來擦除型別
3)通過某種型別容器來擦除型別
4)通過通用型別來擦除型別
5)通過閉包來擦除型別
感興趣的同學可以看
#include <string>
#include <unorderd_map>
#include <memory>
#include <functional>
using namespace std;
#include <Any.cpp>
struct Car{
void test ()const{cout << "car" << endl;}
};
struct Bus{
void test ()const{cout << "bus" << endl;}
};
class IocContainer{
public:
IocContainer(){}
~IocContainer(){}
//註冊需要建立物件的建構函式,需要傳入一個唯一的標識
template<class T, typename Depend>
void RegisterType(const string & key){
//通過閉包擦除型別
std::function<T* ()> function = []{return new T(new Depend());};//lambda表示式
RegisterType(key, function);
}
template<class T>
//根據唯一的標識去查詢對應的構造器,並建立指標物件
T* Resolve(const string& key){
if (m_creator.find(key) == m_creatoe.end())
{
return nullptr;
}
Any resolver = m_creator[key];
//將找到的any轉化為function
std::function<T*()> function = resolver.AnyCast<std::function<T*()>>;
return function;
}
template<class T>
std::shared_ptr<T> ResolveShared(string key){
T* ptr = Resolve<T>(key);
return std::shared_ptr<T>(ptr);
}
private:
void RegisterType(const string& key, Any creator){
if(m_creator.find(key) != m_creator.end){
throw std::invalid_argument("this key has already exist!");
}
m_creator.emplace(key,creator);
}
unorded_map<string,Any> m_creator;
};
struct A{
virtual void func(){}
virtual ~A(){}
};
struct B: public A{
func(){cout << "B" << endl;}
};
struct C:public A{
func(){cout << "C" << endl;}
}
class D{
public:
D(A* a):m_a(a){}
void func(){m_a->func();}
~D(){
if (m_a != nullptr)
{
delete m_a;
m_a = nullptr;
}
}
private:
A* m_a;
};
int _tmain(int argc, _TCHAR* argv[])
{
IocContainer ioc;
//配置依賴關係
ioc.RegisterType<D,B>("B");
ioc.RegisterType<D,C >("C");
auto deriveB = ioc.ResolveShared<D>("B");
deriveB->func();
auto deriveC = ioc.ResolveShared<D>("C");
deriveC->func();
ioc.RegisterType<Car>("car");
ioc.RegisterType<Bus>("bus");
auto bus = ioc.ResolveShared<Bus>("bus");
bus->test();
auto car = ioc.ResolveShared<Car>("car");
car->test();
return 0;
}:
Result:
B
C
bus
car
我們在最開始的第一個例子上添加了兩個類。然後測試。
建立依賴的物件
配置依賴關係來建立物件
ioc.RegisterType<D,B>("B");
ioc.RegisterType<D,C >("C");
但是這種方法不太靈活,一旦建立無法修改。所以還有另外一種方法。
配置引數來建立物件
auto B= ioc.ResolveShared<A>("B");
B->func();
以上說的都是Ioc很基礎也較好理解的一些例子,還有一個更好的例子。能適應多種情況的Ioc的容器的實現。大家可以參考《C++深入應用》P275。