簡體   English   中英

為每個類提取接口是最佳做法嗎?

[英]Is it the best practice to extract an interface for every class?

我見過代碼,其中每個類都有一個它實現的接口。

有時它們並沒有共同的界面。

它們就在那里,它們被用來代替具體的物體。

它們不為兩個類提供通用接口,並且特定於該類解決的問題域。

有什么理由這樣做嗎?

沒有。

接口適用於具有復雜行為的類,如果您希望能夠創建該接口的模擬或偽實現類以用於單元測試,則特別方便。

但是,有些類沒有很多行為,可以更像是值,通常由一組數據字段組成。 為這樣的類創建接口沒有什么意義,因為這樣做會在模擬或提供接口的替代實現時沒有什么意義上引入不必要的開銷。 例如,考慮一個類:

class Coordinate
{
  public Coordinate( int x, int y);
  public int X { get; }
  public int y { get; }
}

您不太可能希望接口ICoordinate與此類一起使用,因為除了簡單地獲取和設置XY值之外,以任何其他方式實現它都沒什么意義。

但是,班級

class RoutePlanner
{
   // Return a new list of coordinates ordered to be the shortest route that
   // can be taken through all of the passed in coordinates.
   public List<Coordinate> GetShortestRoute( List<Coordinate> waypoints );
}

你可能會想一個IRoutePlanner的接口RoutePlanner ,因為有可能被用於規划路線許多不同的算法。

另外,如果你有第三堂課:

class RobotTank
{
   public RobotTank( IRoutePlanner );
   public void DriveRoute( List<Coordinate> points );
}

通過為RoutePlanner一個接口,您可以為RobotTank編寫一個測試方法,該方法使用模擬RoutePlanner創建一個只返回無特定順序的坐標列表的方法。 這將允許測試方法檢查罐在坐標之間正確導航而不測試路線規划器。 這意味着您可以編寫僅測試一個單元(油箱)的測試,而無需測試路線規划器。

你會看到,很容易將真正的坐標輸入這樣的測試而不需要將它們隱藏在ICoordinate接口之后。

重新回答這個問題之后,我決定稍微修改一下。

不,為每個類提取接口不是最佳實踐。 這實際上可能適得其反。 但是,接口很有用,原因如下:

  • 測試支持(模擬,存根)。
  • 實現抽象(進一步到IoC / DI)。
  • 輔助事物,如C#中的co-contra和contra-variance支持。

為了實現這些目標,接口被認為是良好的實踐(並且最后一點實際上是必需的)。 根據項目大小,您會發現您可能永遠不需要與接口通信,或者由於上述原因之一而不斷提取接口。

我們維持一個大型應用程序,其中一些部分很好,一些部分正在遭受缺乏關注。 我們經常發現自己重構將接口拉出類型以使其可測試,或者我們可以更改實現,同時減少該更改的影響。 我們還這樣做是為了減少具體類型可能會在您的公共API不嚴格的情況下意外強加的“耦合”效應(接口只能代表公共API,因此對我們來說固有地變得非常嚴格)。

也就是說,可以在沒有接口的情況下抽象行為,並且可以在不需要接口的情況下測試類型,因此它們不是上述要求 只是您可以用來支持這些任務的大多數框架/庫將有效地對抗接口。


我將留下我的舊答案的背景。

接口定義了公共合同。 實現接口的人必須實現此合同。 消費者只看到公共合同。 這意味着實現細節已經從消費者中抽象出來。

這些天的直接用途是單元測試 接口很容易模擬,存根,假,你的名字。

另一個直接用途是依賴注入 給定接口的注冊具體類型被提供給消費接口的類型。 該類型並不特別關注實現,因此它可以抽象地詢問接口。 這允許您在不影響大量代碼的情況下更改實現(只要合同保持不變,影響區域就非常小)。

對於非常小的項目,我傾向於不打擾,對於中型項目,我傾向於打擾重要的核心項目,而對於大型項目,幾乎每個類都有一個界面。 這幾乎總是支持測試,但在某些情況下注入行為或行為抽象以減少代碼重復。

讓我引用OO大師Martin Fowler為這個帖子中最常見的答案添加一些可靠的理由。

此摘錄來自“企業應用程序架構模式” (參與“編程經典”和“或每個開發必讀”書籍類別)。

[Pattern] 分離界面

(......)

何時使用它

當您需要破壞系統的兩個部分之間的依賴關系時,可以使用分離的接口。

(......)

我遇到過許多開發人員,他們為每個編寫的課程都有單獨的接口。 我認為這是過度的,特別是對於應用程序開發。 保持單獨的接口和實現是額外的工作,尤其是因為您經常需要工廠類(具有接口和實現)。 對於應用程序,我建議僅在您想要破壞依賴關系或希望擁有多個獨立實現時才使用單獨的接口。 如果將接口和實現放在一起並需要稍后將它們分開,這是一個簡單的重構,可以延遲直到你需要這樣做。

回答你的問題:沒有

我自己看過這種類型的“花哨”代碼,開發人員認為他是SOLID,但卻難以理解,難以擴展且過於復雜。

為項目中的每個類提取接口背后沒有實際原因。 這是一個過度殺戮。 它們必須提取接口的原因是它們似乎實現了OOAD原則“ 程序到接口,而不是實現 ”。 您可以在此處找到有關此原理的更多信息。

將界面和編碼添加到界面使得更換實現變得更容易。 這也適用於單元測試。 如果您正在測試一些使用該接口的代碼,您可以(理論上)使用模擬對象而不是具體對象。 這使您的測試更集中,更細粒度。

從我所看到的更常見的是在實際生產代碼中切換測試(模擬)的實現。 是的,這是單元測試的意思。

這可能看起來很愚蠢,但這樣做的潛在好處是,如果在某些時候你意識到有更好的方法來實現某個功能,你可以編寫一個實現相同接口的新類,並將一行更改為使所有代碼都使用該類:分配接口變量的行。

這樣做(編寫一個實現相同接口的新類)也意味着您可以始終在新舊實現之間來回切換以進行比較。

最終你可能永遠不會利用這種便利,而你的最終產品確實只使用為每個界面編寫的原始類。 如果是這樣的話,太好了! 但它真的沒有花太多時間來編寫這些接口,如果你需要它們,它們會為你節省很多時間。

我覺得每個班級都不合理。

這是一個問題,你期望從什么類型的組件重用多少。 當然,你必須計划更多的重用(不需要在以后進行重大的重構)而不是你現在真正要使用的,但為程序中的每個類提取抽象接口意味着你的類比需要。

我喜歡可以通過兩種不同的方式實現的接口,無論是在時間還是空間,即它可以在將來以不同的方式實現,或者在代碼的不同部分中有2個不同的代碼客戶端可能需要不同的實現。

您的代碼的原始編寫者可能只是機器人編碼,或者他們是聰明的並准備版本彈性,或預備單元測試。 更可能是前者,因為版本彈性是一種不常見的需求 - (即,部署客戶端並且無法更改,並且將部署必須與現有客戶端兼容的組件)

我喜歡與我計划測試的其他代碼隔離的依賴項的接口。 如果沒有創建這些接口來支持單元測試,那么我不確定它們是不是一個好主意。 接口需要維護成本,並且當需要將對象與另一個對象交換時,您可能希望將接口僅應用於少數幾個方法(因此更多類可以實現接口),使用抽象可能更好class(以便可以在繼承樹中實現默認行為)。

所以預先需要的接口可能不是一個好主意。

如果是依賴性倒置原則的一部分。 基本上代碼取決於接口而不是實現。

這使您可以輕松地交換實現,而不會影響調用類。 它允許更松散的耦合,這使得系統的維護更加容易。

隨着您的系統不斷發展並變得越來越復雜,這一原則越來越有意義!

如果你想確保能夠在將來注入其他實現,可能會有。 對於一些(可能是大多數)情況,這是過度的,但它與大多數習慣一樣 - 如果你已經習慣了,你就不會花太多時間去做。 而且因為你不能確定你會希望在將來取代,提取每個類的接口確實有一點什么

問題永遠不會只有一個解決方案。 因此,同一接口總是可以有多個實現。

接口很好,因為你可以在(單元)測試時模擬類。

我為至少所有接觸外部資源的類(例如數據庫,文件系統,webservice)創建接口,然后編寫模擬或使用模擬框架來模擬行為。

接口定義了一種行為。 如果實現一個或多個接口,則對象的行為類似於描述的一個或其他接口。 這允許類之間的松散耦合。 當您必須用另一個實現替換實現時,它非常有用。 類之間的通信應始終使用接口來完成,除非類彼此緊密綁定。

為什么需要接口? 實際思考和深刻思考。 接口並不真正附加到類,而是附加到服務。 接口的目標是允許其他人使用您的代碼而不為代碼提供服務。 因此它涉及服務及其管理。

拜拜

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM