2012-12-17 82 views
2

我一直在閱讀關於DI和最佳實踐,但仍未找到這個問題的答案。我應該何時使用接口?依賴注入 - 正確使用接口?

  1. 有些開發人員建議爲每個要注入的對象添加接口。這將構成一個模塊化應用程序。
  2. 其他一些反對這一點。

所以我的問題是哪一個是正確的?

編輯:

以下是雙方的,我還沒有看到使用接口的優勢。在這兩種情況下,我可以很容易地模擬類,並改變實現

使用接口

bind(IUserStorage.class).to(UserStorage.class); 
// Unit test 
bind(IUserStorage.class).to(Mock(UserStorage.class)); 

不使用接口

bind(UserStorage.class).to(UserStorage.class); 
// Unit test 
bind(UserStorage.class).to(Mock(UserStorage.class)); 
+0

請不要爲每個容器管理的依賴項創建一個接口,除非有一些令人信服的理由。如果需要,稍後可以很容易地將類重構爲接口。 –

+0

@aryaxt:爲什麼要創建/使用一個接口破解OOP原則。你能舉一個例子/報價嗎? – Beachwalker

+1

@ default.kramer:只有擁有/維護代碼並且可以更改代碼纔是簡單的。與具有虛擬功能(甚至密封)的類相比,使用接口更符合開放 - 關閉原則。 – Beachwalker

回答

4

我不能相信使用接口時againt OOP原則!

我肯定會在這種情況下使用接口。這意味着你鬆散地耦合你的組件,並且可以輕鬆地模擬和/或替代替代品。許多DI框架將使用這些接口來提供附加功能(例如,創建映射到真實對象的代理對象,但具有附加功能)。

因此我會嘗試使用接口,爲所有,但最瑣碎的注入對象。在某個階段,您將要利用替代性,框架代碼生成等,並且改進界面的使用是一個額外的麻煩,在項目開始時很容易避免。

+0

你會盡可能爲所有注入的對象使用接口嗎? – aryaxt

+0

我肯定會嘗試所有的,但最微不足道的 –

2

如果您使用的接口或至少抽象/可繼承類,你可以通過執行中的DI/IoC的配置容易交換(注入另一個類)更改程序的行爲。 使用接口是一種很好的做法(imho)。如果你正在編寫需要模擬的UnitTests,這一點尤其重要。如果你不使用接口,編寫UnitTests的覆蓋面很難(在大多數「現實世界」情況下不能說是不可能的)。

我想你應該使用的接口,如果有可能是一個機會,注入部分可能會改變。應該很容易擴展您的實現,請參見Open-Closed-Principle。 =>這將需要交換模塊/部件/實現......問問自己如果你的類沒有虛擬函數來覆蓋會發生什麼,你不得不改變實現。

我會使用接口至少爲公共類 /您的代碼的一部分(其他程序員將使用的部分)。

在看看你的樣品。 問題出在接線部分,而不僅僅是綁定作爲接口的(默認)實現(綁定工作,但接線可能會中斷)。

例如,如果你有2個實現(這裏的C#示例,在Java中應該是相同的。,太):

public interface IUserStorage 
{ 
    void Write(object something); 
} 

public class UserStorageTextFile : IUserStorage 
{ 
    public void Write(object something) { ... }; // stores to text file 
} 

public class UserStorageDB : IUserStorage 
{ 
    public void Write(object something) { ... }; // stores to DB 
} 

public class MyStorageClient 
{ 
    public MyStorageClient(IUserStorage storage) { ... } // copy to private field and use it etc. 
} 

取決於你的IoC它應該很容易接線 MyStorageClient的實例來你IUserStorage結合。

bind(IUserStorage.class).to(UserStorageDB.class); // Java sample, eh? 

但如果你MyStorageClient強烈被迫使用DB已經...

public class MyStorageClient 
{ 
    public MyStorageClient(UserStorageDB storage) { ... } // copy to private field and use it etc. 
} 

...這是IMPOSIBLE與除UserStorageTextFile的UserStorageTextFile類是從UserStorageDB繼承接線起來。 ..但爲什麼你應該有一個依賴於例如如果你只想寫一個簡單的文本文件,Oracle驅動程序(UserStorageDB需要)?

我認爲樣本不夠清晰,顯示了使用接口的好處...

,但如果沒有...嘗試這樣做:基於

bind(UserStorageDB.class).to(UserStorageTextFile.class); 

// and in another config/module/unitTest 
bind(UserStorageTextFile.class).to(Mock(UserStorageDB.class)); 

// and try to wire it against your client class, too (both ways, meaning one config for TextFile and load a config for the DB after changing only the configuration) 
3

界面設計是國際奧委會的基石,這裏是基於接口的設計的簡要說明(遺憾的是,我引用我自己的博客,但我剛剛完成了一個article這一點,這是從我的碩士學位論文的摘錄):

Nandigam et al。將基於接口的設計定義爲「一種開發面向對象系統的方式,在這種系統中,人們有意識地並主動地定義了 ,並在設計中儘可能使用接口來獲得使用接口進行設計的好處」[Nan09]。採用基於接口的設計的應用遵循「編程接口, 不是實現」的原則。該原理具有以下優點 到所得到的系統[Dav03]:靈活性(描述了系統 魯棒性改變),擴展(與系統可 容納增加的容易性)和可插拔(能力允許 在運行時用相同的接口替換對象)。

一旦你與的IoC混合接口設計可以得到以下好處:

  1. 任務是分離從實現。
  2. 增加模塊性其中模塊僅依賴於其合約(接口)上的其他模塊。
  3. 增加可插拔性並且替換模塊不會對其他模塊產生級聯效應。

要回答你的問題,我會使用不同類型的模塊的接口。例如,每個服務或存儲庫一個。

我不爲控制器或模型類(MVC應用程序)創建接口。

所有這些,作爲副作用,便於測試

+0

這正是我目前正在做的,ViewController和模型沒有被注入,所以我不會將接口應用到他們。 – aryaxt

-1

你的問題指出「一些開發者[是爲了這個]」和「一些開發者[反對這個]」,所以沒有正確的答案。但這就是爲什麼我同意interfaces are overused

如果你正在創建一個庫,選擇何時使用接口很重要。如果您不控制代碼的消耗方式,那麼創建可維護的合同就更加困難。

但是,如果你正在創建一個應用程序,它不太可能需要一個接口,因爲一個類的公共接口可以作爲消費代碼的可維護協定。比方說,第1版看起來是這樣的:

public class UserStorage 
{ 
    public void Store(User user) { /* ... */ } 
} 

你甚至都不需要重構工具將它改成這樣:

public interface UserStorage 
{ 
    public void Store(User user); 
} 

class TheImplementation implements IUserStorage 
{ 
    public void Store(User user) { /* ... */ } 
} 

然後你就可以輕鬆地使用重構工具的接口重命名爲IUserStorage

所以當你正在編寫的非庫代碼,通常可以用類脫身,直到你需要交換的實現,裝飾等在類的公有接口不適合你應該使用的接口中需要。 (例如,請參閱interface segregation principle

簡而言之 - 具有與類1:1的接口在應用程序代碼中是不必要的間接方向。

+0

但是,如果您的UserStorage默認實現需要一堆依賴性來工作,那麼這很糟糕......這也會將這些依賴關係添加到您的指派類(即使沒有合乎邏輯的原因)。此外,您的示例不顯示任何虛擬方法,因此改變行爲並使用關鍵字「新」(公共新的無效存儲(用戶用戶)...)將很難找到錯誤。 – Beachwalker

+0

那麼這是java,所以虛擬是默認的...但重點是,在重構前版本中只有一個implementation_。它仍然很容易嘲笑。 –