104

术语“泄漏抽象”是什么意思?(请举例说明。我经常很难理解一个单纯的理论。)

4

11 回答 11

115

这是一个肉类空间示例:

汽车对驾驶员有抽象。最纯粹的形式是方向盘、油门和刹车。这种抽象隐藏了很多关于引擎盖下的细节:发动机、凸轮、正时皮带、火花塞、散热器等。

这种抽象的巧妙之处在于,我们可以用改进的部分替换部分实现,而无需重新培训用户。假设我们用电子点火代替了分电器盖,我们用可变凸轮代替了固定凸轮。这些变化提高了性能,但用户仍然使用方向盘并使用踏板来启动和停止。

这实际上是相当了不起的……一个 16 岁或 80 岁的人可以操作这台复杂的机器,而不知道它是如何工作的!

但是有泄漏。传输是一个小泄漏。在自动变速箱中,您可以感觉到汽车在换档时会暂时失去动力,而在 CVT 中,您会一直感觉到平稳的扭矩。

还有更大的泄漏。如果发动机转速过快,可能会损坏它。如果发动机缸体过冷,汽车可能无法启动或性能不佳。如果你同时转动收音机、前照灯和空调,你会看到你的油耗下降。

于 2011-06-23T02:37:58.767 回答
57

它只是意味着您的抽象暴露了一些实现细节,或者您在使用抽象时需要了解实现细节。该术语归因于约 2002 年的Joel Spolsky 。有关更多信息,请参阅维基百科文章

一个典型的例子是允许您将远程文件视为本地文件的网络库。使用此抽象的开发人员必须意识到网络问题可能会导致其失败,而本地文件则不会。然后,您需要开发代码来处理网络库提供的抽象之外的特定错误。

于 2010-10-07T15:03:03.397 回答
15

维基百科对此有一个很好的定义

泄漏抽象是指任何已实现的抽象,旨在降低(或隐藏)复杂性,其中底层细节并未完全隐藏

或者换句话说,对于软件来说,当您可以通过程序中的限制或副作用来观察功能的实现细节时。

一个简单的例子是 C# / VB.Net 闭包和它们无法捕获 ref / out 参数。无法捕获它们的原因是由于提升过程如何发生的实现细节。这并不是说有更好的方法来做到这一点。

于 2010-10-07T15:05:28.903 回答
14

下面是一个 .NET 开发人员熟悉的示例:ASP.NET 的Page类试图隐藏 HTTP 操作的细节,尤其是表单数据的管理,这样开发人员就不必处理发布的值(因为它会自动将表单值映射到服务器控制)。

但是,如果您超出最基本的使用场景,Page抽象就会开始泄漏,并且除非您了解类的实现细节,否则很难使用页面。

一个常见的例子是将控件动态添加到页面 - 除非您在正确的时间添加它们,否则不会为您映射动态添加的控件的值:在底层引擎将传入的表单值映射到适当的控件之前。当您必须了解这一点时,抽象已经泄漏

于 2010-10-07T15:51:58.843 回答
10

好吧,在某种程度上,这只是一个纯粹的理论问题,尽管并非不重要。

我们使用抽象使事情更容易理解。我可能会以某种语言对字符串类进行操作,以隐藏我正在处理作为单个项目的有序字符集的事实。我处理一组有序的字符以隐藏我正在处理数字的事实。我处理数字是为了隐藏我正在处理 1 和 0 的事实。

泄漏的抽象是一种不隐藏其要隐藏的细节的抽象。如果在 Java 或 .NET 中对 5 个字符的字符串调用 string.Length,我可以得到 5 到 10 之间的任何答案,因为这些语言调用字符的实现细节实际上是 UTF-16 数据点,可以表示 1 或.5 个字符。抽象已经泄露。但是不泄漏它意味着找到长度将需要更多的存储空间(以存储实际长度)或从 O(1) 变为 O(n) (以计算实际长度是多少)。如果我关心真正的答案(通常您并不真正关心),您需要了解真正发生的事情。

更有争议的情况发生在诸如方法或属性让您进入内部工作的情况下,无论它们是抽象泄漏,还是转移到较低抽象级别的明确定义的方法,有时可能是人们不同意的问题。

于 2010-10-07T15:11:54.917 回答
9

我将继续使用 RPC 提供示例。

在理想的 RPC 世界中,远程过程调用应该看起来像本地过程调用(或者故事是这样的)。它对程序员应该是完全透明的,这样当他们调用时,SomeObject.someFunction()他们不知道SomeObject(或仅someFunction就此而言)是本地存储和执行还是远程存储和执行。理论认为这使编程更简单。

现实是不同的,因为进行本地函数调用(即使您使用的是世界上最慢的解释语言)和:

  • 通过代理对象调用
  • 序列化你的参数
  • 建立网络连接(如果尚未建立)
  • 将数据传输到远程代理
  • 让远程代理恢复数据并代表您调用远程功能
  • 序列化返回值
  • 将返回值传输到本地代理
  • 重新组装序列化的数据
  • 从远程函数返回响应

仅在时间上,这大约是三个数量级(或更多!)的数量级差异。这三个以上数量级将对性能产生巨大影响,这将使您对过程调用的抽象很明显在您第一次错误地将 RPC 视为真正的函数调用时泄漏。此外,除非代码中出现严重问题,否则真正的函数调用在实现错误之外几乎没有故障点。RPC 调用具有以下所有可能的问题,这些问题将作为失败案例出现,超出您对常规本地调用的期望:

  • 您可能无法实例化您的本地代理
  • 您可能无法实例化您的远程代理
  • 代理可能无法连接
  • 您发送的参数可能无法使其完整或根本没有
  • 远程发送的返回值可能不会使其完整或根本

因此,现在您的“就像本地函数调用”的 RPC 调用有一大堆额外的失败条件,您在执行本地函数调用时不必应对。抽象再次泄露,甚至更严重。

最后,RPC 是一个糟糕的抽象,因为它在每一层都像筛子一样泄漏——成功时和失败时。

于 2010-10-07T16:12:08.397 回答
5

什么是抽象?

抽象是简化世界的一种方式。这意味着您不必担心幕后或幕后实际发生的事情。这意味着某些东西是白痴证明。

抽象的例子:飞行 737/747 的复杂性被“抽象”掉了

飞机是非常复杂的机械部件。你有喷气发动机、氧气系统、电气系统、起落架系统等,但飞行员不必担心喷气发动机的复杂性……所有这些都是“抽象的”。这意味着飞行员只需要担心飞机的转向:左转左转,右转右转,向上拉以获得高度,向下推以下降。

很简单……其实我撒了谎:控制方向盘有点复杂。在理想的世界中,这是飞行员唯一应该担心的事情。但现实生活中并非如此:如果你像猴子一样驾驶飞机,对飞机的运行方式或任何实施细节没有任何真正的了解,那么你很可能会坠毁并杀死机上所有人。

737 示例中的泄漏抽象

实际上,飞行员确实需要担心很多重要的事情——并不是所有的事情都被抽象出来了:飞行员必须担心风速、推力、迎角、燃料、高度、天气问题、下降角度以及是否飞行员正朝着正确的方向前进。计算机可以帮助飞行员完成这些任务,但并非一切都是自动化/简化的。

例如,如果飞行员在柱子上拉得太猛 - 飞机会服从,但飞行员会冒着使飞机失速的风险,一旦失速,在飞机坠落到地面之前很难重新控制它.

换句话说,对于飞行员来说,仅仅控制方向盘而不知道其他任何事情是不够的……不……不……飞行员必须知道潜在的风险和局限性。飞机在飞行员飞行之前一架......飞行员必须知道飞机是如何工作的,飞机是如何飞行的;飞行员必须知道实施细节.....飞行员必须知道拉得太猛会导致失速,或者太陡峭的着陆会毁坏飞机。

这些东西不是抽象出来的。很多东西都被抽象掉了,但不是所有的东西。飞行员只需要担心转向柱,也许还有其他一两件事。抽象是“泄漏的”。

代码中的泄漏抽象

......这在你的代码中是一样的。如果你不知道底层的实现细节,那么你往往会陷入困境。

下面是一个编码示例:

ORM 抽象了处理数据库查询的很多麻烦,但如果你曾经做过类似的事情:

User.all.each do |user|
   puts user.name # let's print each user's name
end

然后你会意识到如果你有超过几百万的用户,那么这是杀死你的应用程序的好方法。并不是所有的东西都被抽象掉了。您需要知道,User.all与 2500 万用户通话会导致您的内存使用量激增,并且会导致问题。你需要知道一些潜在的细节。抽象是泄漏的。

于 2019-11-28T02:44:52.967 回答
4

django ORM 多对多示例中的一个示例:

请注意,在示例 API 使用中,您需要先 .save() 基本 Article 对象 a1,然后才能将 Publication 对象添加到多对多属性。请注意,更新多对多属性会立即保存到底层数据库,而更新单个属性在调用 .save() 之前不会反映在数据库中。

抽象是我们正在使用一个对象图,其中单值属性和多值属性只是属性。但是,作为关系数据库支持的数据存储的实现会泄漏……因为 RDBS 的完整性系统是通过对象接口的薄薄的单板出现的。

于 2010-10-07T15:48:30.977 回答
1

事实上,在某些时候,将由您的规模和执行来指导,您将需要熟悉抽象框架的实现细节,以了解它为什么会以这种方式运行。

例如,考虑这个SQL查询:

SELECT id, first_name, last_name, age, subject FROM student_details;

及其替代方案:

SELECT * FROM student_details;

现在,它们看起来确实是逻辑上等价的解决方案,但由于单独的列名规范,第一个解决方案的性能更好。

这是一个微不足道的例子,但最终还是回到了 Joel Spolsky 的名言:

在某种程度上,所有重要的抽象都是泄漏的。

在某个时候,当您的操作达到一定规模时,您会想要优化 DB (SQL) 的工作方式。为此,您需要了解关系数据库的工作方式。一开始它对你来说是抽象的,但它是有漏洞的。你需要在某个时候学习它。

于 2018-10-10T09:25:53.973 回答
-1

假设我们在库中有以下代码:

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);
}
于 2017-02-24T13:13:55.253 回答
-3

泄漏抽象就是封装状态。泄漏抽象的非常简单的例子:

$currentTime = new DateTime();

$bankAccount1->setLastRefresh($currentTime);
$bankAccount2->setLastRefresh($currentTime);
$currentTime->setTimestamp($aTimestamp);

class BankAccount {
    // ...

    public function setLastRefresh(DateTimeImmutable $lastRefresh)
    {
        $this->lastRefresh = $lastRefresh;
    } }

和正确的方式(不是泄漏抽象):

class BankAccount
{
    // ...

    public function setLastRefresh(DateTime $lastRefresh)
    {
        $this->lastRefresh = clone $lastRefresh;
    }
}

更多描述在这里

于 2018-07-13T17:29:40.643 回答