我們為什么要使用空接口

      網友投稿 893 2025-03-31

      轉自 http://www.cnblogs.com/shanyou/archive/2005/10/22/259674.html


      FxCop設計規則中的第三條提供了對空接口的檢查.下面是它的描述:

      一個接口提供了一組行為和使用契約(usage contract),任何一個類型都可以實現這個Interface, 而不需要考慮這個類型的繼承層次。一個類型通過實現接口的成員而實現這個接口。一個空的接口沒有定義任何成員,因此,也就沒有任何契約能夠被實現。

      如果你的設計包含一個空的接口,并且希望一些類型實現這個接口,你很可能希望使用這個接口作為一個標記來標示一組類型。如果你只需要區分這些類型在運行時,一個更佳的解決方式是使用自定義屬性(attribute)。使用有或沒有一個屬性或通過屬性的字段(Property)去標示一組類型。如果你希望這種標示能夠被使用在編譯時,就只好使用空接口了。

      這說明在大多數情況下,空接口都說明在設計上存在錯誤。這里有一個例子:

      interface ThingBase {}; interface Thing1 : ThingBase { // Operations here... }; interface Thing2 : ThingBase { // Operations here... };

      1

      2

      3

      4

      5

      6

      7

      8

      考察這個定義,我們可以觀察到兩個事實:

      ? Thing1 和Thing2 有共同的基類,因此是相關的。

      ? 不管Thing1和Thing2有什么共同之處,都可以在ThingBase接口中找到。

      當然,只要看一看ThingBase,我們就會發現Thing1 和Thing2 根本沒有共享任何操作,因為ThingBase 是空的。 假如我們是在使用面向對象模型,這種做法就顯然很奇怪:在面向對象模型中,與某個對象通信的唯一途徑是向它發送消息。但要發送消息,我們需要有操作。假如ThingBase沒有操作,我們就無法向它發送消息,而Thing1 和Thing2 也就是不相關的,因為它們沒有共同的操作。但看到Thing1 和Thing2 有共同的基類,我們就會得出這樣的結論:它們是相關的,否則共同的基類就根本不會存在。到了這里,大多數程序員都會開始撓頭,想知道到底在發生什么事情。

      使用這樣的設計的一個常見理由是,要多態地處理Thing1 和Thing2。

      例如,我們可以繼續先前的定義:

      interface ThingUser { void putThing(ThingBase thing); };

      1

      2

      3

      4

      現在使用共同的基類的目的就清楚了:我們既想要把Thing1 代理、也想要把Thing2 代理傳給putThing。這能否證明使用空的基接口是正當的?

      要回答這個問題,我們需要思考一下在putThing 的實現中發生的事情。顯然, putThing 不可能調用ThingBase 上的操作,因為在那里沒有操作。這意味, putThing 必須要能做以下兩件事情之一:

      putThing 能夠記住事物的值。

      putThing 能夠試著向下轉換到Thing1 或Thing2,然后調用操作。

      putThing 的偽碼實現看起來可能像是這樣:

      void putThing(ThingBase thing) { if (is_a(Thing1, thing)) { // Do something with Thing1... } else if (is_a(Thing2, thing)) { // Do something with Thing2... } else { // Might be a ThingBase? // ... } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      這個實現試著依次把它的參數向下轉換成每種可能的值,直到它找 到參數實際的運行時類型。當然,任何一本像樣的面向對象課本都會告 訴你,這是在濫用繼承,并且會帶來維護問題。如果你發現自己在編寫像putThing 這樣的操作,依賴于人為的基接口,問問你自己,你是否真的需要采用這種做法。例如,這樣的設計可能更加適宜:

      interface Thing1 { // Operations here... }; interface Thing2 { // Operations here... }; interface ThingUser { void putThing1(Thing1 thing); void putThing2(Thing2 thing); };

      1

      2

      3

      4

      5

      6

      7

      8

      我們為什么要使用空接口

      9

      10

      在這種設計中, Thing1 和Thing2 是不相關的,而ThingUser 為每種類 型的代理提供了單獨的操作。這些操作的實現不需要使用任何向下轉換,而且在我們的面向對象世界里,一切都安然無恙。

      下面是空的基接口的另一種常見用法:

      interface PersistentObject {}; interface Thing1 : PersistentObject { // Operations here... }; interface Thing2 : PersistentObject { // Operations here... };

      1

      2

      3

      4

      5

      6

      7

      8

      9

      顯然,這種設計把持久功能放在PersistentObject 基接口中,并且要求想要擁有持久狀態的對象繼承PersistentObject。表面上,這是合理的:畢竟,這樣使用繼承是一種沿用已久的設計模式,那么,它可能有什么問題?我們發現,這種設計有這樣一些問題:

      ? 上面的繼承層次用來給 Thing1 和Thing2 增加行為。但在嚴格的OO 模型中,行為只能通過發送消息來調用。這引發了這樣一個問題:PersistentObject 實際上該怎樣著手完成它的工作;推測起來,它對Thing1 and Thing2 的實現(也就是,內部狀態)有所了解,所以它可以把該狀態寫入數據庫。但如果是這樣, PersistentObject、Thing1,以及Thing2 就不能再在不同的地址空間中實現了,因為如果是那樣, PersistentObject 就不再能知道Thing1 和Thing2 的狀態。

      換一種做法, Thing1 和Thing2 可以使用PersistentObject 提供的某種功能, 使它們的內部狀態持久。但PersistentObject 沒有任何操作,那么Thing1 和Thing2 實際上又該怎樣去完成這件事情呢?再一次,唯一可行的做法是,在同一個地址空間中實現PersistentObject、Thing1,以及Thing2,并讓它們在幕后共享實現狀態,也就是說,它們不能在不同的地址空間中實現。

      ? 上面的繼承層次把世界分成兩半,一個含有持久對象,另一個含有非持久對象。這種做法有著深遠的影響:

      ? 假定你有一個應用,它已經實現了一些非持久對象。隨著時間推移,需求發生變化,你發現現在你想讓部分對象持久。采用上面的設計,你無法做到這一點,除非你改變你的對象的類型,因為它們現在必須繼承PersistentObject。這當然是一個極其糟糕的消息:你不僅要改變你的服務器中的對象的實現,還要找到并更新所有正在使用你的對象的客戶,因為它們突然有了一種全新的類型。更糟糕的是,你無法讓它們向后保持兼容:或者讓所有客戶隨著服務器發生改變,或者一個客戶都不改變。要想讓某些客戶“不升級”,這是不可能的。

      ? 這種設計不能擴展到支持多種特性。設想一下,我們有另外一些行為,對象可以繼承它們,比如序列化、容錯、持久,以及用搜索引擎進行搜索的能力。我們很快就會陷入多重繼承的泥淖。更糟糕的是,每種可能的特性組合都會創建一種完全獨立的類型層次。這意味著,你不再能編寫出一些操作,一般化地對一些對象類型進行操作。例如,你不能把持久對象傳到需要非持久對象的地方, 即使對象的接收者并不在乎對象的持久方面。這很快就會造成碎片化的、難以維護的類型系統。不久,你會發現,你不是在重寫應用,就是獲得了某種難以使用也難以維護的東西。

      但愿前面的討論成為一個警告:空接口幾乎總是表明,你的應用通過所定義的接口之外的機制共享了實現狀態。如果你發現自己在編寫空的接口定義,你至少應該后退一步,思考一下手上的問題;其他設計可能會更加適宜,更能清晰地表達你的意圖。如果無論如何你都要使用空接口,那么要注意,你幾乎肯定會失去這樣的能力:改變對象模型在物理的服務器進程上的分布方式,因為你無法把共享了隱藏狀態的接口分置在不同的地址空間中。

      評論列表

      1樓 2005-10-22 10:46 microhf

      這么說吧:

      public interface IIdentifier { }

      正如樓主所說目的就是“作為一個標記來標示一組類型”,僅此而已。如果硬要弄這樣的東西

      public interface IIdentifier2:IIdentifier { }

      1

      類說話,其實是很腦缺水,硬是自己的誤用把自己搞混,要造兩個標識,不就這樣了

      public interface IIdentifier { } public interface IIdentifier2 { }

      1

      2

      有什么問題呢?

      至于后面樓主說的,恰是在對interface的誤用中找問題。

      interface和繼承沒有什么關系,它是實現面向對象的“多態”的一種方式,自然沒有你說的那些繼承和多重繼承的問題。

      interface是一組行為的一種契約,這種契約與對于不同的實現者(class)之間可以是沒有什么關系的。對于繼承來說所有的子類之間都是存在某種關系的,繼承最大的問題就是誤用(尤其多重繼承),override看似很酷,很多時候它使對象之間關系混亂和復雜。

      支持(0)反對(0)

      2樓 2005-10-22 15:52 kain

      在控件設計時也比較有用

      支持(0)反對(0)

      3樓 2005-10-23 18:46 裝配腦袋

      我認為空接口在運行時沒有任何意義,屬于不良設計。當且僅當需要編譯時類型信息和編譯時判定,才需要空接口。

      支持(1)反對(0)

      4樓 2005-10-23 23:35 microhf

      同意 裝配腦袋 說的

      支持(0)反對(0)

      5樓 2005-10-24 11:04 Klesh Wong

      INamingContainer 接口

      標識在 Page 對象的控件層次結構內創建新 ID 命名空間的容器控件。這僅是一個標記接口。

      ——-摘自MSDN

      是否考慮過以上這種需求。因為需要在頁面中保證控件的ID唯一性。那么會要求所有控件的子控件有唯一的ID,此是否為之契約?然而你要向IDE說明你已經做到這一點,能保證自己的子控件都有唯一的ID,是不是需要實現契約?但實現這種契約事實上是不需要任何方法調用的。所以這應該算是空接口的合理用法與存在的合理性吧。

      支持(0)反對(0)

      6樓[樓主] 2005-10-24 19:04 張善友

      小殘說的非常正確,還有IBatisNet的DataAccess框架也是一個空接口IDao.

      支持(0)反對(0)

      7樓 2005-10-25 09:34 砂砂

      小殘說的也是,我就是一直沒想通INamingContainer 接口是怎么工作的:(

      支持(0)反對(0)

      #8樓 2006-11-01 18:59 yyww[匿名]

      我看空接口也沒用,就算要打標記可以通過attribute來進行

      支持(0)反對(0)

      9樓 2008-11-30 00:34 Jianqiang Bao

      篩選文章時看到此文,我也說幾句。

      空接口作用大大的有。

      1.IssueVision中的觀察者就是空接口實現。只是為了把若干Observer打上標記

      2.Memento中為了將自身方法只暴露給指定的類,一定要加一個空接口的“殼”

      支持(0)反對(0)

      10樓 2008-12-22 16:54 不入流程序員

      iserror

      支持(0)反對(0)

      11樓 2013-09-05 10:00 鶴沖天

      支持下!

      給出這個地址,方便查閱:CA1040:避免使用空接口

      https://msdn.microsoft.com/zh-cn/library/ms182128(v=vs.110).aspx

      支持(0)反對(0)

      面向對象編程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:表單制作
      下一篇:史上最走心的Webpack4.0中級教程——配置之外你應該知道事
      相關文章
      亚洲中文字幕日产乱码高清app | 极品色天使在线婷婷天堂亚洲| 亚洲美女色在线欧洲美女| 亚洲人成色777777在线观看| 亚洲日韩国产成网在线观看| 亚洲人成影院在线观看| 亚洲黄黄黄网站在线观看| 国产成人综合亚洲| 久久久久亚洲精品无码网址色欲| 亚洲AV第一成肉网| 午夜在线亚洲男人午在线| 亚洲?v无码国产在丝袜线观看 | 亚洲av日韩综合一区久热| 亚洲AV无码专区亚洲AV桃| 亚洲成av人无码亚洲成av人| 亚洲爆乳无码精品AAA片蜜桃| 亚洲国产成人久久综合| 老牛精品亚洲成av人片| 亚洲AV之男人的天堂| 亚洲综合色成在线播放| 在线亚洲人成电影网站色www| 中文字幕人成人乱码亚洲电影| 亚洲精品乱码久久久久久| 亚洲国产精品无码专区在线观看| 亚洲第一区香蕉_国产a| 亚洲精品午夜在线观看| 精品亚洲AV无码一区二区三区 | 亚洲欧洲日产国码av系列天堂 | 色偷偷尼玛图亚洲综合| 亚洲国产成人精品女人久久久| 国产精品亚洲mnbav网站 | 性色av极品无码专区亚洲| 偷自拍亚洲视频在线观看| 亚洲日本中文字幕天堂网| 亚洲一区二区三区在线观看精品中文| 国产AV无码专区亚洲Av| 337p日本欧洲亚洲大胆色噜噜| 亚洲一卡二卡三卡| 亚洲国产成人综合精品| 亚洲精品成人片在线观看| 九月丁香婷婷亚洲综合色|