2011-09-26 84 views
64

我有一個函數返回相同種類的對象(查詢結果),但沒有屬性或方法的共同點。爲了有一個通用的類型,我使用了一個空接口作爲返回類型,並在兩者上「實現」。空接口代碼味道?

這聽起來不對。我只能抱緊自己,希望有一天這些類會有共同的東西,我會把這個共同的邏輯移到我的空白界面。然而,我並不滿意,並在考慮是否應該採用兩種不同的方法,並有條件地接下來打電話。這會是一個更好的方法嗎?

我也被告知.NET Framework使用空接口進行標記目的。

我的問題是:是一個空的界面設計問題的一個強有力的標誌或被廣泛使用?

編輯:對於那些有興趣的人,我後來發現,功能語言中的歧視工會是我嘗試實現的完美解決方案。 C#對這個概念看起來並不友善。

編輯:我寫了一個關於這個問題的longer piece,詳細解釋了這個問題和解決方案。

+14

這些被稱爲[標記接口](http://en.wikipedia.org/wiki/Marker_interface_pattern),顯然它們被廣泛使用。 – BoltClock

+3

閱讀此http://msdn.microsoft.com/en-us/library/ms182128%28v=vs.80%29.aspx(我的意見需要不同的方法) – V4Vendetta

+1

東西在類似的線http:// stackoverflow。 com/questions/835140/using-marker-classes-to-control-logic-flow – V4Vendetta

回答

39

雖然看起來這個用例存在一種設計模式(現在很多人提到過「標記接口」),但我相信這種做法的使用是代碼氣味的指示(大部分時間在最小)。

正如@ V4Vendetta貼,還有的是,針對這種靜態分析規則: http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

如果您的設計包括類型有望實現,你可能正在使用一個接口作爲標記或空接口一種識別一組類型的方法。如果此標識將在運行時發生,則正確的方法是使用自定義屬性。使用屬性的存在或不存在或屬性的屬性來標識目標類型。 如果標識必須在編譯時發生,那麼使用空接口是可以接受的。

這是引述MSDN推薦:

取下接口或成員添加到它。如果正在使用空接口標記一組類型,請使用自定義屬性替換該接口。

這也反映了已發佈維基百科鏈接的評論部分。

標記接口的一個主要問題是接口定義了實現類的契約,並且該契約被所有子類繼承。這意味着你不能「實現」一個標記。在給出的例子中,如果你創建了一個你不想序列化的子類(可能是因爲它依賴於臨時狀態),你必須求助於顯式拋出NotSerializableException(每個ObjectOutputStream文檔)。

+1

我認爲在我的使用案例(只有兩個類,不可能派生),它似乎沒問題。查看我的澄清編輯。 –

+0

我接受這個答案。儘管其他答案也提供了有價值的信息,但這篇文章主要闡述了該方法潛在的問題。 –

+1

正如在其他地方已經指出的那樣,雖然'屬性'是這樣做的'正確'方式,但它們實施和使用會更加尷尬,實際上這會妨礙它們的使用。 – nicodemus13

6

你回答了你自己的問題......「我有一個函數根據某些情況返回完全不同的對象。」...爲什麼你想要有相同的函數返回完全不同的對象?我看不出有用的理由,也許你有一個好的,在這種情況下,請分享。

編輯:考慮到你的澄清,你應該確實使用標記界面。 「完全不同」與「同一種」完全不同。如果他們完全不同(不僅僅是他們沒有共享成員),那將是一種代碼味道。

+0

這看起來像一個更好的候選人作爲評論。我在我的問題中添加了一個澄清部分。如果您需要其他任何東西,請提問。 –

7

如果沒有用作marker interface,我會說是的,這是一種代碼異味。

一個接口定義了實現者遵守的契約 - 如果你有沒有使用反射的空接口(就像標記接口一樣),那麼你可以使用Object作爲(已經存在的)基本類型。

+1

'object'將會過於通用,並且不會提供返回「kind」的提示。界面幫助我縮小解釋函數返回值的選項。但我理解你的評論與缺乏關於我要返回的對象的相似性的澄清有關。 –

+0

@ssg - 挺。如果您將這些對象傳遞給一個方法來對它們進行操作,則必須有與這些對象相似的東西,否則您應該有單獨的方法。 – Oded

9

你說你的函數「根據某些情況返回完全不同的對象」 - 但它們有多不同?能不能是一個流編寫器,另一個是UI類,另一個是數據對象?不......我懷疑它!

您的對象可能沒有任何常用的方法或屬性,但它們在角色或用法上可能相似。在這種情況下,marker interface似乎完全合適。

+0

他們是不同類型的查詢結果,但都是查詢結果,是的。 –

+1

行 - 我認爲,他們有一個共同的角色,即使他們不包含共同的屬性。聽起來像一個標記界面是正確的! – ColinE

3

正如很多人可能已經說過的那樣,空接口確實可以有效地用作「標記接口」。

我能想到的最好的用法可能是將對象表示爲屬於特定子域的對象,由相應的Repository處理。假設你有不同的數據庫來檢索數據,並且你有一個Repository實現。一個特定的Repository只能處理一個子集,不應該從任何其他子集獲得一個對象的實例。你的域模型可能是這樣的:那麼

//Every object in the domain has an identity-sourced Id field 
public interface IDomainObject 
{ 
    long Id{get;} 
} 

//No additional useful information other than this is an object from the user security DB 
public interface ISecurityDomainObject:IDomainObject {} 

//No additional useful information other than this is an object from the Northwind DB 
public interface INorthwindDomainObject:IDomainObject {} 


//No additional useful information other than this is an object from the Southwind DB 
public interface ISouthwindDomainObject:IDomainObject {} 

您的倉庫可以製成通用的ISecurityDomainObject,INorthwindDomainObject和ISouthwindDomainObject,然後你有一個編譯時檢查你的代碼是不是試圖通過一個安全對象爲Northwind DB(或任何其他排列)。在這種情況下,即使沒有提供任何實施合同,界面也會提供有關班級性質的寶貴信息。