“泄漏抽象”是什么意思?(请举例说明。我常常很难理解一个纯粹的理论。)
“泄漏抽象”是什么意思?(请举例说明。我常常很难理解一个纯粹的理论。)
Answers:
这是个Meatspace示例:
汽车有驾驶员抽象。最纯粹的形式是方向盘,油门和制动器。这种抽象隐藏了引擎盖下的很多细节:引擎,凸轮,同步带,火花塞,散热器等。
关于此抽象的整洁之处在于,我们可以用改进的部分替换实现的某些部分,而无需重新培训用户。假设我们用电子点火装置取代了分配器盖,而用可变凸轮取代了固定凸轮。这些变化提高了性能,但用户仍然可以用方向盘操纵并使用踏板来启动和停止。
这实际上是非常了不起的……16岁或80岁的人可以操作这种复杂的机器,而无需真正了解其内部工作原理!
但是有泄漏。变速箱泄漏很小。在自动变速器中,您会感觉到汽车在换档时会暂时失去动力,而在CVT中,您会一直感觉到平稳的扭矩。
也有更大的泄漏。如果发动机转速过快,可能会损坏发动机。如果发动机缸体太冷,则汽车可能无法启动或性能不佳。如果同时打开收音机,前灯和交流电,您会发现油耗降低了。
这仅表示您的抽象公开了一些实现细节,或者在使用抽象时您需要了解实现细节。该术语归因于Joel Spolsky(大约在2002年)。有关更多信息,请参见Wikipedia文章。
网络库是一个典型的例子,它使您可以将远程文件视为本地文件。使用此抽象的开发人员必须意识到,网络问题可能会导致失败,而本地文件不会失败。然后,您需要开发代码以专门处理网络库提供的抽象之外的错误。
这是.NET开发人员熟悉的示例:ASP.NET的 Page
类尝试隐藏HTTP操作的细节,尤其是表单数据的管理,以便开发人员不必处理发布的值(因为它会自动将表单值映射到服务器)控件)。
但是,如果您超出了最基本的使用场景,那么Page
抽象就会开始泄漏,除非您了解类的实现细节,否则页面将变得难以使用。
一个常见的示例是在页面上动态添加控件-除非您在正确的时间添加它们,否则动态映射控件的值将不会为您映射:在基础引擎将传入的表单值映射到适当的控件之前。当您必须学习时,抽象就泄漏了。
嗯,从某种意义上说,这虽然不重要,但纯粹是理论上的事情。
我们使用抽象使事情更容易理解。我可能会使用某种语言在字符串类上进行操作,以掩盖我正在处理作为单个项目的一组有序字符这一事实。我处理一组有序的字符以隐藏我正在处理数字的事实。我处理数字以掩盖我正在处理1和0的事实。
泄漏抽象是不会隐藏其要隐藏的细节的抽象。如果在Java或.NET中以5个字符的字符串调用string.Length,则我可以从5到10得到任何答案,因为实现细节表明这些语言称呼字符实际上是可以表示1或16的UTF-16数据点。 .5个字符。抽象已泄漏。但是,不泄漏它意味着找到长度将需要更多的存储空间(以存储实际长度),或者从O(1)变为O(n)(以计算出实际长度)。如果我在乎真正的答案(通常不是真的),则需要了解实际情况。
在某些情况下,更有争议的情况发生,例如方法或属性让您进入内部工作时,无论它们是抽象泄漏,还是定义到较低抽象级别的明确定义的方法,有时都可能会引起人们的不同意见。
我将继续使用RPC进行示例。
在RPC的理想世界中,远程过程调用应该看起来像本地过程调用(或者这样)。对程序员来说应该是完全透明的,这样,当他们打电话时,SomeObject.someFunction()
他们不知道是SomeObject
(还是仅someFunction
出于此目的)是本地存储和执行还是远程存储和执行。从理论上讲,这使编程更简单。
实际情况有所不同,因为进行本地函数调用(即使您使用的是世界上最慢的解释语言)之间也存在巨大差异:
仅在时间上,大约是三个数量级(或更多!)的数量级差异。这三个数量级以上将在性能上产生巨大的差异,这将使您对过程调用抽象的泄漏相当明显,这是您第一次错误地将RPC视为真实函数调用时。再有一个真正的函数调用,除非代码中存在严重问题,否则实现错误之外的故障点将很少。RPC调用具有以下所有可能的问题,这些失败问题会超出常规本地调用所期望的故障范围,而会逐渐失效:
因此,现在您的RPC调用“就像本地函数调用一样”具有全部额外的失败条件,您在进行本地函数调用时不必面对这些额外的失败条件。抽象再次泄漏,甚至更加困难。
最终,RPC是一个糟糕的抽象,因为它在每个级别都像筛子一样泄漏–成功和失败时都会泄漏。
django ORM多对多示例中的一个示例:
请注意,在“示例API使用情况”中,您需要先将.Article()基本对象a1保存(save()),然后才能将发布对象添加到“多对多”属性。并且请注意,更新多对多属性会立即保存到基础数据库,而更新单个属性不会在db中反映出来,除非调用.save()。
抽象是我们正在使用对象图,其中单值属性和多值属性只是属性。但是作为关系数据库支持的数据存储的实现会泄漏……因为RDBS的完整性系统通过对象接口的薄面板出现。
首先,最好是了解什么是“抽象”?
抽象是简化世界的一种方式。这意味着您不必担心引擎盖下/幕后的实际情况。这意味着有些东西是白痴证明。好的,那是什么意思?最好通过示例说明。
抽象示例:“抽象”了飞行737/747的复杂性
让我们以波音客机为例。这些飞机是非常复杂的机械零件。您拥有喷气发动机,氧气系统,电气系统,起落架系统等,但是飞行员不必担心喷气发动机的复杂性……所有这些东西都“被抽象化了”,这意味着:那天,飞行员只担心轮子和控制柱来控制飞机。从左向左走,从右向右走,向上拉高以升高,向下推以下降。这很简单……实际上我撒了谎:控制方向盘要稍微复杂一点。在理想的世界里,那是他唯一的选择应该担心。但这在现实生活中并非如此:如果您像猴子一样驾驶飞机,而对飞机的运作方式或任何实施细节没有任何真正的了解,那么您很可能会坠毁并杀死机上的所有人。
实际上,飞行员确实需要担心很多重要的事情-并非一切都被抽象化了:飞行员必须担心风速,推力,迎角,燃料,高度,天气问题,下降角度,是否飞行员正朝着正确的方向前进,飞机现在就在那里,依此类推。计算机可以帮助飞行员完成这些任务,但并非所有操作都是自动化/简化的。
例如,如果飞行员在立柱上用力过猛-飞机会服从,但是飞行员将有使飞机失速的风险;如果使飞机失速,则在重新坠落至地面之前很难重新获得控制权。
换句话说,仅凭飞行员仅在不知道其他任何内容的情况下控制方向盘是不够的。在他飞行之前。……她必须知道飞机的工作原理以及飞机的飞行方式。他必须知道实施细节.....她必须知道过分用力拉扯会导致失速,或者过分着陆会破坏飞机。
这些东西没有被抽象掉。很多东西都被抽象掉了,但不是全部。飞行员只需要担心转向柱,也许还需要担心一两件事。抽象是“泄漏的”。
......这与您的代码相同。如果您不了解底层的实现细节,那么您经常会陷入困境。
这是编码示例:
ORM在处理数据库查询时会带来很多麻烦,但是如果您曾经做过类似的事情:
User.all.each do |user|
puts user.name # let's print each user's name
end
然后,您将意识到,如果拥有超过数百万的用户,这是杀死应用程序的好方法。并非一切都被抽象掉了。您需要知道,User.all
与2500万用户进行通话会增加您的内存使用率,并会引起问题。您需要了解一些基本细节。抽象是泄漏的。
事实上,在某个时候,这将取决于您的规模和执行情况,您需要熟悉抽象框架的实现细节,以便理解其行为方式。
例如,考虑以下SQL
查询:
SELECT id, first_name, last_name, age, subject FROM student_details;
及其替代方案:
SELECT * FROM student_details;
现在,它们看起来确实在逻辑上是等效的解决方案,但是由于单独的列名规范,第一个解决方案的性能更好。
这是一个琐碎的例子,但最终回到了Joel Spolsky的话:
在某种程度上,所有非平凡的抽象都是泄漏的。
在某个时候,当您达到一定规模的操作时,您将需要优化数据库(SQL)的工作方式。为此,您将需要了解关系数据库的工作方式。一开始它是抽象给您的,但是很漏水。您需要在某个时候学习它。
假设我们在库中有以下代码:
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);
}
泄漏的抽象全都与封装状态有关。泄漏抽象的非常简单的示例:
$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;
}
}
这里有更多描述。