2

我讀Miško Hevery's Guide: Writing Testable Code,如果「在構造函數完成後未完全初始化對象(注意初始化方法)」,它會聲明一個警告標誌。依賴注入和初始化方法

比方說,我寫了一個Redis包裝類,它有一個接受主機名和端口的init方法。根據Miško的說法,這是一個警告信號,因爲我需要調用它的init方法。

我正在考慮的解決方案如下: 對於需要這種初始化的每個類,創建一個具有創建類的Create方法的工廠類,並調用其init方法。

現在代碼:

class Foo 
{ 
    private IRedisWrapper _redis; 
    public Foo(IRedisWrapper redis) 
    { 
     _redis = redis; 
    } 
} 
.... 
IRedisWrapper redis = new RedisWrapper(); 
redis.init("localhost", 1234); 
Foo foo = new Foo(redis); 

我會使用類似::

class Foo 
{ 
    private IRedisWrapper _redis; 
    public Foo(IRedisWrapper redis) 
    { 
     _redis = redis; 
    } 
} 
.... 
RedisWrapperFactory redisFactory = new RedisWrapperFactory(); 
IRedisWrapper redisWrapper = redisFactory.Create(); 
Foo foo = new Foo(redisWrapper); 

我使用Simple Injector作爲IOC框架,這使得這個上面,而不是使用喜歡的事的解決probelmatic - 在這種情況下,我會使用類似的東西:

class Foo 
{ 
    private RedisWrapper _redis; 
    public Foo(IRedisWrapperFactory redisFactory) 
    { 
     _redis = redisFactory.Create(); 
    } 
} 

我真的很喜歡聽取您對上述解決方案的意見。

感謝

回答

3

也許我誤解了你的問題,但我不認爲簡單注射器是一個限制因素。由於構造函數應該像little as possible一樣執行,所以不應該在構造函數中調用Create方法。這甚至是一件奇怪的事情,因爲工廠是爲了延遲創建一個類型,但是由於在構造函數中調用Create,所以創建不會被延遲。

Foo構造函數應該簡單地依靠IRedisWrapper,你應該提取這樣的redisFactory.Create()呼叫您的DI配置:

var redisFactory = new RedisWrapperFactory(); 

container.Register<IRedisWrapper>(() => redisFactory.Create()); 

但由於工廠的唯一目的是防止整個應用程序重複的初始化邏輯,它現在已經失去了它的目的,因爲工廠使用的唯一地方是在DI配置中。於是我們可以拋出工廠出和寫入以下注冊:

container.Register<IRedisWrapper>(() => 
{ 
    IRedisWrapper redis = new RedisWrapper(); 
    redis.init("localhost", 1234); 
    return redis; 
}); 

我們現在放置匿名委託內部的Create方法的主體。你的RedisWrapper類目前有一個默認的構造函數,所以這種方法很好。但是如果RedisWrapper開始獲得它自己的依賴關係,最好讓容器創建該實例。這可以如下進行:

container.Register<IRedisWrapper>(() => 
{ 
    var redis = container.GetInstance<RedisWrapper>(); 
    redis.init("localhost", 1234); 
    return redis; 
}); 

當你需要創建後進行初始化類,如RedisWrapper顯然需要時,諫的做法是使用RegisterInitializer方法。最後的代碼片段可以寫成如下:

container.Register<IRedisWrapper, RedisWrapper>(); 

container.RegisterInitializer<RedisWrapper>(redis => 
{ 
    redis.init("localhost", 1234); 
}); 

此註冊RedisWrapper當請求的IRedisWrapper要返回和RedisWrapper與註冊初始化初始化。此註冊可防止隱藏的回調到容器。這提高了性能,並提高了容器的能力爲diagnose your configuration

+0

太棒了,這正是我正在尋找的答案 - 謝謝! – kernix

+0

我想澄清的最後一件事 - 當Misko聲明「在構造函數完成後未完全初始化對象(注意初始化方法)」 - 他的意思是什麼? – kernix

+0

這是關於[時間耦合](http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling/)。這是一種設計氣味。 – Steven

0

如果RedisWrapperFactory在一些其他層(它從DB /文件/部分服務獲取數據)來定義,該代碼將殺死dependncy注射的目的。你的圖層變得直接依賴於另一個。此外,這是不可測試的,因爲你不能創建一個模擬/假的對象進行測試。很明顯,您不想在測試中執行真正的數據庫操作或I/O讀寫或服務調用。

你可能想要做這樣的事情......

class Foo 
{ 
    private IRedisWrapper _redis; 

    public Foo(IRedisWrapperFactory redisFactory) 
    { 
     _redis = redisFactory.Create(); 
    } 
} 

在其他層

public class RedisWrapperFactory : IRedisWrapperFactory 
{ 
    public IRedisWrapper Create() 
    { 
     var r = RedisWrapper(); 
     r.Init("localhost", 1234); //values coming from elsewhere 
     return r; 
    }         
} 

在你的引導程序()或的Application_Start()方法,注入工廠,像

container.Register<IRedisWrapperFactory, RedisWrapperFactory>(); 
+0

這正是我的意思 - 你在哪裏看到差異? – kernix

+0

唯一的區別是在Foo構造函數中使用的接口而不是具體的類。因此,爲了測試,你可以模擬/僞造接口的實際實現,並且玩弄它來測試Foo的行爲,而不是RedisWrapperFactory! –

+0

編輯問題時,這是一個錯字,謝謝。 – kernix

0

由於RedisWrapperFactory作爲依賴關係似乎不太正確,所以sinc e這不是你想要的工廠。除非你需要傳遞給Create()的具體參數。

我不知道Simple Injector,但我會建議如果它不允許你自定義創建你的對象來使用你的工廠,你可能想看看其他一些DI框架。我使用StructureMap,但也有其他人可供選擇。

編輯:說了這麼多,如果IRedisWrapper合同是它必須在一些特定的方法初始化調用構造函數後,它看起來有點古怪,如果你在使用Foo它不調用init()。特別是對於熟悉IRedisWrapper(很多人)的人,而不是與該特定應用程序的IOC設置(不是很多人)。當然,如果你打算使用工廠,就像Arghya C所說的那樣,也可以通過一個界面來使用它,否則你實際上並沒有實現任何目的,因爲你無法選擇你正在注入的哪一個IRedisWrapper