53

術語「泄漏抽象」是什麼意思? (請用例子來解釋,我經常很難理解單純的理論)泄漏抽象的含義?

+0

的[流利的接口和抽象泄漏]可能重複(http://stackoverflow.com/questions/433450/fluent-interfaces-and-leaky-abstractions) – missingfaktor 2010-10-07 15:07:51

+12

您可能需要閱讀喬爾斯波斯基的原創文章[泄漏抽象法則](http:// www。joelonsoftware.com/articles/LeakyAbstractions.html),據我所知該術語的起源。 – 2010-10-07 15:14:43

+1

擬議的大部分答案都是流暢的界面。 – 2010-10-07 15:20:03

回答

33

它只是意味着您的抽象公開了一些實現細節,或者您在使用抽象時需要了解實現細節。該術語歸因於Joel Spolsky,大約在2002年。有關更多信息,請參見wikipedia article

一個典型的例子是網絡庫,它允許您將遠程文件視爲本地文件。使用此抽象的開發人員必須注意,網絡問題可能會導致此問題以本地文件不支持的方式失敗。然後您需要開發代碼來處理網絡庫提供的抽象之外的特定錯誤。

+6

@mehaase我不明白你的抽象是設計漏洞還是漏洞,這很重要。我已經通過參考文章中的示例和更多信息擴展了答案,以便它可以獨立運行。此外,我不認爲「漏洞抽象」必然是貶義。對我而言,它僅僅描述了一種情況,作爲開發人員,在處理抽象時需要更加小心。設計可能是好的,壞的或無關緊要的,而與「泄漏」無關。 – tvanfosson 2011-09-22 18:09:50

10

維基百科有這個

一個漏水的抽象是指任何實現的抽象,意在減少(或隱藏)的複雜性,其中底層細節不完全隱藏在pretty good definition

還是在換句話說,對於軟件來說,您可以通過程序中的限制或副作用來觀察功能的實現細節。

一個簡單的例子就是C#/ VB.Net關閉以及它們無法捕獲ref/out參數。他們不能被捕獲的原因是由於提升過程如何發生的實現細節。這並不是說有一個更好的方法來做到這一點。

7

那麼,從某種意義上說,這純粹是理論上的事情,儘管並不重要。

我們使用抽象使事情更易於理解。我可能使用某種語言的字符串類來操作,以隱藏我正在處理單個項目的有序字符集的事實。我處理一組有序的字符以隱藏我正在處理數字的事實。我處理數字以隱藏我正在處理1和0的事實。

泄漏抽象是一個不隱藏其隱藏的細節。如果在Java或.NET中以5個字符的字符串調用string.Length,我可以從5到10得到任何答案,因爲這些語言稱爲字符的實現細節實際上是UTF-16數據點,它可以表示1或.5的一個字符。抽象已經泄露。雖然沒有泄漏,但意味着查找長度需要更多的存儲空間(存儲實際長度)或者從O(1)變爲O(n)(計算真實長度)。如果我關心真正的答案(通常你不是真的),你需要了解真正發生的事情。

更多有爭議的案例發生在像方法或屬性讓你進入內部工作的情況下,無論它們是抽象泄漏還是明確定義的移動到較低抽象級別的方法,有時都可能是一個問題人們不同意。

+2

而你用1和0來隱藏你正在使用電子和物理的事實(我已經很晚評論了) – Davy8 2011-09-22 18:12:41

2

一個例子在django ORM many-to-many example:在您需要對.save()基礎Article對象A1,然後才能添加發布對象的許多一對多屬性樣本API使用

通知。並注意更新多對多屬性會立即保存到底層數據庫,而在調用.save()之前更新單數屬性不會反映到數據庫中。

抽象是我們正在處理對象圖,其中單值屬性和多值屬性只是屬性。但是作爲關係數據庫支持的數據存儲的實現泄漏......因爲RDBS的完整性系統通過對象接口的薄單板出現。

9

下面是.NET開發人員熟悉的一個例子:ASP.NET的Page類試圖隱藏HTTP操作的細節,特別是表單數據的管理,以便開發人員不必處理髮布的值(因爲它會自動將表單值映射到服務器控件)。

但是,如果你偏離最基本的使用場景,Page抽象開始泄漏,除非你瞭解類的實現細節,否則很難與頁面一起工作。

一個常見的例子是向頁面動態添加控件 - 動態添加的控件的值不會被映射爲您,除非您在恰好時間處添加它們:在底層引擎映射傳入表單值之前到適當的控制。當你必須知道,抽象有泄漏

5

我將繼續使用RPC給出示例。

在理想的RPC環境中,遠程過程調用應該看起來像本地過程調用(或故事情節)。對於程序員來說,它應該是完全透明的,因此當他們撥打SomeObject.someFunction()時,他們不知道SomeObject(或只是someFunction)在本地存儲和執行或遠程存儲和執行。理論認爲,這使得編程更簡單。

現實的情況是不同的,因爲有和撥打本地函數調用(即使你使用了世界上最慢的解釋型語言)之間的巨大差異:

  • 通過代理對象
  • 序列化呼喚你參數
  • 進行網絡連接(如果尚未建立)
  • 將數據傳輸到所述遠程代理
  • 具有遠程代理恢復數據並調用遠程功能,以您的名義
  • 序列化的返回值(一個或多個)
  • 發射的返回值的本地代理
  • 重組序列化的數據
  • 返回從遙控功能的響應

僅僅在這個時間裏,大約有三個等級(或更多!)的差異。這三個+數量級將會在性能上產生巨大的差異,這會使得你抽象出一個過程調用泄漏,而這顯然是你第一次錯誤地將RPC視爲一個真正的函數調用。進一步的一個真正的函數調用,除了代碼中的嚴重問題之外,在執行錯誤之外幾乎沒有任何故障點。RPC調用所有這一切將得到厚厚地塗在作爲失敗的案例之上你會從一個普通本地呼叫的期望以下可能出現的問題:

  • 你可能不能夠實例化本地代理
  • 你可能不能夠實例化遠程代理
  • 的代理可能無法連接
  • 你發送的信息可不能讓它完整的參數或者根本
  • 返回值的遠程發送可能不會使它完好無損或者完全沒有問題

因此,現在您的RPC調用「就像本地函數調用」一樣,有一個額外的失敗情況,您在進行本地函數調用時不必與之競爭。抽象再次泄露,甚至更難。

最後RPC是一個糟糕的抽象,因爲它在每個級別都像篩子一樣泄漏 - 當成功並且兩者都失敗時。

+0

我喜歡Erlang的方法,因爲它不會試圖掩蓋差異在函數調用和發送消息到進程之間,以至於兩者使用非常不同的語法。遠程進程消息發送與本地進程發送非常明顯不同,儘管使用相同的一般語法。 2010-10-07 16:19:13

+2

嗯,這是實際給出一個很好的例子(閱讀理解,人)的唯一反應,所以它得到我的+1。 – 2011-06-23 02:05:09

64

這裏有一個meatspace例如:

汽車有司機抽象。它最純粹的形式有方向盤,加速器和制動器。這個抽象隱藏了許多關於引擎蓋下的內容的細節:引擎,凸輪,同步帶,火花塞,散熱器等。

關於這種抽象的巧妙之處在於我們可以用改進的零件代替部分實現再培訓用戶。假設我們用電子點火來代替分配器蓋,並且我們用固定凸輪替換固定凸輪。這些改變提高了性能,但用戶仍然駕駛車輪,並使用踏板來啓動和停止。

這實際上是非常了不起的......一個16歲或80歲的孩子可以操作這個複雜的機器而不真正知道它是如何工作的!

但有泄漏。傳輸是一個小泄漏。在自動變速器中,您可以感受到汽車在切換檔位時瞬間失去動力,而在CVT中,您始終感受到平穩的扭矩。

也有更大的泄漏。如果你的發動機轉速過快,你可能會損壞它。如果發動機缸體太冷,汽車可能無法啓動或性能不佳。如果你在同一時間啓動收音機,頭燈和AC,你會看到你的汽油里程減少。

+6

感謝您的示例。沒有人能夠提供簡單的解釋。 – 2013-06-12 21:28:07

+6

這是一個很好的答案,特別是因爲它展示了用戶的觀點,這就是軟件版本的全部內容。 – chad 2013-07-08 21:10:33

0

假設,我們有一個庫下面的代碼:

Object[] fetchDeviceColorAndModel(String serialNumberOfDevice) 
{ 
    //fetch Device Color and Device Model from DB. 
    //create new Object[] and set 0th field with color and 1st field with model value. 
} 

當消費者調用API,他們得到一個Object []。消費者必須明白,對象數組的第一個字段具有顏色值,第二個字段是模型值。這裏抽象已經從庫中泄露給消費者代碼。

其中一個解決方案是返回封裝設備的模型和顏色的對象。消費者可以調用該對象來獲取模型和顏色值。

DeviceColorAndModel fetchDeviceColorAndModel(String serialNumberOfTheDevice) 
{ 
    //fetch Device Color and Device Model from DB. 
    return new DeviceColorAndModel(color, model); 
}