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); 
}