漏抽象的意义?

“抽象抽象”是什么意思? (请用实例来解释,我经常很难理解一个理论。)

这是一个肉食空间的例子:

汽车有司机的抽象。 最纯粹的forms有方向盘,加速器和制动器。 这个抽象隐藏了很多关于引擎盖,发动机,凸轮,同步带,火花塞,散热器等的细节。

关于这个抽象的简单的事情是,我们可以用改进的部分replace部分实现,而不需要再训练用户。 假设我们用电子点火来replace分配器盖,然后用可变凸轮replace固定凸轮。 这些改变提高了性能,但用户仍然驾驶车轮,并使用踏板来启动和停止。

这实际上是非常了不起的…一个16岁或80岁的人可以操作这个复杂的机器,而不用真正了解它是如何工作的!

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

还有更大的泄漏。 如果发动机转速过快,可能会损坏发动机。 如果发动机缸体太冷,汽车可能无法启动或性能不佳。 如果你同时打开收音机,头灯和空调,你会看到你的汽油里程减less。

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

一个经典的例子是networking库,它允许你把远程文件当作本地文件。 使用此抽象的开发人员必须知道,networking问题可能会导致失败,本地文件不会。 然后,您需要开发代码来处理networking库提供的抽象之外的特定错误。

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

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

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

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

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

但是,如果你在最基本的使用场景之外漫游, Page抽象开始泄漏,除非你了解类的实现细节,否则很难与页面一起工作。

一个常见的例子是dynamic添加控件到页面 – dynamic添加的控件的值将不会被映射为您,除非您在恰当的时间添加它们:在底层引擎将传入的表单值映射到适当的控件之前。 当你必须知道,抽象已经泄漏

那么从某种意义上说,这纯粹是理论上的事情,尽pipe并不重要。

我们使用抽象来使事情更容易理解。 我可能使用某种语言的string类来操作,以隐藏事实,即我正在处理一组有序的字符,这些字符是单个项目。 我处理一组有序的字符来隐藏我正在处理数字的事实。 我处理数字来隐藏我正在处理1s和0s的事实。

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

如果某个方法或属性让你进入内部工作,无论是抽象泄漏,还是定义明确的抽象方法,都会引发更多的争议,有时这些事情可能是人们不同意的事情。

我将继续用RPC来给出例子。

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

实际情况是不同的,因为在进行本地函数调用(即使使用世界上最慢的解释型语言)之间存在巨大差异:

  • 通过代理对象调用
  • 序列化您的参数
  • build立networking连接(如果尚未build立)
  • 将数据传输到远程代理
  • 让远程代理恢复数据并代表您调用远程function
  • 序列化返回值(s)
  • 将返回值传输到本地代理
  • 重新组装序列化的数据
  • 返回来自远程函数的响应

仅仅在这个时间里,这个数量级就差不多了三个(或者更多)。 这三个+数量级将会使性能发生巨大的变化,这将使得你的程序调用泄漏的抽象显然是你第一次错误地将RPC视为一个真正的函数调用。 进一步的真正的函数调用,除了代码中的严重问题之外,在执行错误之外几乎没有任何故障点。 一个RPC调用具有以下所有可能的问题,这些问题会被视为超出常规本地调用期望的失败情况:

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

所以现在你的RPC调用“就像本地函数调用”一样,有一个额外的失败情况,你不必在进行本地函数调用的时候进行竞争。 抽象再次泄露,更难。

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

在Django的ORM多对多的例子中的一个例子 :

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

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

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

 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 []。 消费者必须明白,对象数组的第一个字段有颜色值,第二个字段是模型值。 这里的抽象已经从库泄漏到消费者代码。

其中一个解决scheme是返回一个封装了器件型号和颜色的对象。 消费者可以调用该对象来获得模型和颜色值。

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