通过抽象隐藏细节有什么价值?透明度没有价值吗?


30

背景

我不是抽象的忠实粉丝。我会承认,人们可以从接口的适应性,可移植性和可重用性中受益。那里确实有好处,我不想质疑这一点,所以让我们忽略它。

抽象还有另一个主要的“好处”,即向该抽象的用户隐藏实现逻辑和细节。论据是您不需要了解细节,并且此时人们应该专注于自己的逻辑。从理论上讲是有道理的。

但是,无论何时维护大型企业应用程序,我总是需要了解更多细节。只是为了确切地找出某物在做什么,就变得越来越麻烦,它不断地深入研究抽象。也就是说,在找到所使用的存储过程之前,必须执行“打开声明”大约12次。

这种“隐藏细节”的心态似乎只是阻碍。我一直希望界面更透明,抽象更少。我可以阅读高级源代码并知道它的作用,但是我永远不知道它是如何执行的,何时执行的,是我真正需要知道的。

这里发生了什么?我曾经使用过的每个系统是否都经过了错误的设计(至少从这个角度而言)?

我的理念

当我开发软件时,我觉得我尝试遵循一种与ArchLinux哲学密切相关的哲学

Arch Linux保留了GNU / Linux系统固有的复杂性,同时使它们井井有条,透明。Arch Linux开发人员和用户认为,试图隐藏系统的复杂性实际上会导致系统更加复杂,因此应避免使用。

因此,我从未尝试将软件的复杂性隐藏在抽象层的后面。我试图滥用抽象,而不是成为抽象的奴隶。

心中的问题

  1. 隐藏细节有真正的价值吗?
  2. 我们不是在牺牲透明度吗?
  3. 这种透明度难道不有价值吗?

7
抽象可能以不良设计的形式被滥用。但这并不意味着抽象原则上没有价值。
伯纳德

4
我认为这里有一个很好的问题,但是,它听起来很像反对抽象的咆哮。您是否可以不加强调,并提出更多实际问题。
PersonalNexus

4
您确定使用的是“隐藏细节”的正确定义吗?在这种情况下,这是有关减少耦合的问题,而不是阻止您学习某些事物的内部原理。
Andres F.

24
除非您喜欢在办公桌旁使用电压表和示波器进行编程,否则您只能针对抽象之上的抽象进行编程。隐藏您实际上不是在操作位而是实际上在操作电压的细节是否对您有价值?这样做会牺牲透明度吗?这种透明度有价值吗?
埃里克·利珀特

8
我认为您遇到的问题不是抽象,而是真正没有抽象任何东西的空的间接层。是的,那些通常在大型企业系统中发现,不是,它们不是很好。
Michael Borgwardt 2012年

Answers:


47

隐藏细节的原因不是要保留细节。这样就可以在不破坏相关代码的情况下修改实现。

想象一下,您有一个对象列表,每个对象都有一个Name属性和其他数据。很多时候,您需要在列表中找到Name与某个字符串匹配的项目。

一种明显的方法是逐个循环遍历每个项目,并检查是否Name与字符串匹配。但是,如果您发现这花费了太多时间(例如,如果列表中有数千个项目的话),那么您可能希望将其替换为字符串对象字典查找。

现在,如果您所有的查找都是通过检索列表并对其进行遍历来完成的,则您需要做大量的工作来解决此问题。如果您在图书馆中并且第三方用户正在使用它,那就更难了。您不能出去修复他们的代码!

但是,如果您有一种FindByName方法来封装名称查找过程,则只需更改其实现方式,所有调用它的代码将继续工作,并免费获得更快的速度。那才是抽象和封装的真正价值。


我同意这一点,但这不是问题的重点。我完全同意封装特定功能的方法很有用,但是我觉得这并不总是动机。我错了吗?
user606723,2012年

3
@ User606723:应该是。这并不意味着它始终。有些人不明白要点,他们只是在更多的层上堆了更多的层,然后把事情弄得一团糟。这就是为什么经验丰富的程序员建议新的开发人员在了解新技术之前不要采用新技术。
梅森惠勒2012年

3
@ user606723:透明度鼓励紧密耦合,因此虽然不一定总是不好,但通常是这样。
Malfist 2012年

3
梅森(Mason)所描述的问题是,人们以一种崇尚货物的编程方式堆积在层上,这就是为什么我对太多的继承感到怀疑,以及为什么应该偏重于组成而不是继承。对于Java程序员而言,这似乎尤其成问题。
2012年

2
不,紧密耦合并不总是坏的。它通常很糟糕,但是竭尽全力避免它会产生更严重的问题,例如软编码。
梅森惠勒

16

我刚读完“代码完成”中有关抽象的部分,因此大多数资源都来自此。

抽象的目的是消除询问“这是如何实现的”的需求。当您致电时user.get_id(),您将知道an id是您要取回的东西。如果您必须问“这如何获得用户ID?” 那么您可能不需要idget_id()返回意外的且设计不佳的内容。

您可以使用抽象来设计:

a house with doors and windows

没有设计

a box with four walls,
    with 3 holes,
        two of which fit panes of glass surrounded by wood frames,
        one that fits a large plank of wood with hinges and a metal knob,
etc.

我不是在谈论这些接口。这些接口很好。我说的是庞大的复杂系统,这些系统细分为许多不同的抽象。
user606723,2012年

9
@ user606723然后,您的问题是关于过度复杂的设计,而不是抽象
Andres F.

3
+1完成代码。它既涵盖了为什么必须进行抽象设计,又因为错误的抽象水平阻碍了设计。为了进一步说明您的例子,如果我在分区办公室工作,我想考虑一下您的房子,而不必多加掩饰。但是,如果您以我的方式考虑房屋,那么您将无法建造房屋。
Spencer Rathbun 2012年

该问题应该关闭或标题更改
Jake Berger

8

隐藏细节有真正的价值吗?

是。通过呈现抽象,我们可以在更高层次上进行思考和编程。

想象一下没有演算或矩阵代数的物理系统建模。这是完全不切实际的。同样,如果我们只能在标量级别编程,则将无法解决有趣的问题。甚至相对简单的Web应用程序也可以从诸如标签库之类的抽象中极大地受益。与重复创建四个文本字段和一个选择框相比,插入一个表示“地址输入字段”的标记要容易得多。而且,如果您决定扩展到海外,则可以修改标签定义,而不用固定每种形式来处理国际地址。有效使用抽象使某些程序员的效率是其他程序员的十倍。

人类的工作记忆有限。抽象使我们能够对大型系统进行推理。

Aren't we sacrificing transparency?

否。如果不使用抽象,则软件组件的目的将埋在重复的细节中。开发人员花了很多时间在这样的代码上:

for (i = 0; i < pilgrim.wives.size(); ++i) {
  wife = pilgrim.wives[i];
  for (j = 0; j < wife.sacks.size(); ++j) {
     sack = wife.sacks[i];
     for (k = 0; j < sack.cats.size(); ++j) {
        cat = sack.cats[k];
        for (m = 0; m < cat.kits.size(); ++m) {
           ++count;
        }
     }
  }
}

并思考“哦,是的,套件上还有另一个四级循环”,而不是看到

pilgrim.kits.each { ++count; }

这种透明度难道不有价值吗?

正如您指出的那样,间接调用需要付出一定的代价。创建图层以防万一是没有意义的。使用抽象来减少重复并澄清代码。


7

当人们说抽象掩盖了实现细节时,它们实际上并不是很难找到的“隐藏”。它们的意思是将实现细节与公共接口分开,以使接口保持简单,简洁和可管理。就像汽车“隐藏”其大部分重要部件,并仅提供一组相当基本的控件来操作它们一样,软件模块“隐藏”其大部分功能在其肠道深处,并且仅向用户公开有限数量的访问方法。开车。想象一下一辆汽车,您必须手动操作引擎的所有内部零件(并且其中很多都是令人毛骨悚然的),您将很难监视交通情况并找到路。

但是,保持界面简单不仅是美学上的事情;它可以使成功的项目与死亡之行有所不同。让我们扮演恶魔的拥护者一分钟;想象一个完全没有任何抽象的软件项目。如果您需要保留一个值,则可以使用全局变量。如果您需要多次使用功能,则可以将其复制粘贴。如果您需要某个代码节的两个不同版本,请复制粘贴,将其包装在if语句中,然后修改两个分支。从技术上讲,它是可行的,但是在接下来的几个月中,您将要解决一些非常讨厌的问题:

  • 当您发现并修复错误时,它可能也存在于其他类似代码的复制粘贴实例中,因此,除了查找和修复错误外,您还必须寻找其他事件并加以修复。
  • 为了查找错误或实施更改,维护程序员必须能够理解相关代码。这样做的难度随着相关代码段的大小而增加,但随着范围的扩大而增加。在脑子里一步步走一些代码的同时,把六个变量放在脑子里是可行的。但是如果有几百个,它们的生产率会受到严重影响(我想将思想过程与一个程序用尽,该程序的物理RAM不足并且必须插入交换文件中:而不是一口气地阅读代码,程序员必须来回跳动才能查找内容)。
  • 一段代码的范围也影响了为发现错误而必须挖掘的代码库的大小。如果您有一个带有两个参数的十行函数,并且没有全局变量,并且知道输入和崩溃的行的值,那么查找错误通常很简单,通常只需要查看代码即可。如果它有几百行,二十个参数,十五个全局变量,并且调用了其他一些性质相似的函数,那么您将感到非常痛苦。
  • 没有适当的抽象,任何更改都可能潜在地影响代码库的大部分,因为实际上任何事情都可能取决于要更改的代码。这种代码库的典型症状是,您做了很小的看似无害的更改,并且一个完全不相关的功能突然中断了。使用抽象,您可以限制更改可能造成的损害,并使影响更可预测。如果更改私有字段的名称,则只有一个源文件需要检查;否则,请执行以下操作。如果更改全局变量的名称,则需要遍历整个代码库。

在抽象性很差的代码库中,影响通常会随代码库的大小呈指数增长,也就是说,添加恒定数量的代码会使维护工作量增加恒定因子。更糟的是,在项目中增加更多的程序员并不能线性地提高生产率,但是最多只能以对数方式提高生产率(因为团队越大,沟通所需的开销就越大)。


2

我认为您应该了解它的工作原理(如果需要)。一旦确定了它可以完成的功能,您就可以放心了。我从没想过要把它藏起来永远。

一旦您在时钟上设置了可以确信会工作的闹钟,便会睡一觉,因为它会在正确的时间响起。提早一个小时醒来只是为了让您看几秒钟就浪费了。


1
可以,但是我通常不会被要求更改闹钟的工作方式,或者让其他人在未完全了解更改的情况下更改闹钟的工作方式。
user606723,2012年

1
您是否看到过曾经使用过的每个框架的代码更改?
JeffO 2012年

1
不,但是我并没有为我使用过的每个框架维护代码更改。
user606723 2012年

3
您已经充分证明了JeffO的观点:您也不是在维护闹钟。如果您购买了一个并且没有进行完整的拆解和分析其工作原理就开始使用它,那么您已经接受了内幕对您来说将是抽象的。没有什么可以说您不能拆开它来发现它是如何工作的,但是您多久发现它是必要的呢?
Blrfl 2012年

2

要具体回答您的问题:

隐藏细节有真正的价值吗?

是。正如您在问题的第一行中所确认的那样。

我们不是在牺牲透明度吗?

并不是的。如果需要的话,写得好的抽象将使理解细节变得容易。

这种透明度难道不有价值吗?

是。应该设计和实现抽象,以便在需要/需要时易于理解细节。


最后我喜欢一个答案。
user606723 2012年

1

我想说的是,当隐藏的东西起作用时,隐藏细节非常好。

例如,假设我们开发了一个定义行为的接口(即GetListItems,SendListItem),这是用户通过单击某些按钮或某些操作启动的两个功能。现在,每个用户可以拥有自己的“ ListItemStore”。一个人在Facebook上,一个人在myspace上。(例如),并说它已通过用户偏好保存为用户属性/在应用程序中的某个位置设置。并且说应用程序开发人员有可能在整个过程中添加其他ListItemStore。时间(mybook,facespace等)

现在有很多细节可以连接到Facebook并获取它们的物品..同样,当连接到myspace时也有很多细节..依此类推...

现在,在编写了初始的“商店访问权”代码之后,可能不需要进行修改(在facebook中,我们可能需要一名专职开发人员来跟上更改,zing ..),

因此,当您使用代码时,它类似于:

    new ItemManager(user) //passes in user, allowing class to get all user properties
    ItemManager.GetListItems()

现在您已经从用户存储数据的任何地方获取了数据,由于我担心的是获取项目列表并对其进行处理,并且仅用了两行,无论如何添加了更多的商店,我可以回到在堆栈上回答/张贴问题...大声笑..

因此,实现这一目标的所有途径都是“隐藏的”,只要我获得正确的项目列表,谁会真正在意它的执行方式。如果您有单元测试,那么您甚至可以放轻松些,因为结果应该已经被量化了。


是的,但是需要维护的部分永远不会是这两行。这些都是不可能的。您总是需要将其更改为第二,第三,第四级。我同意,当您拥有可以信任的稳定 API时,这很棒,但是如果仅凭业务的复杂性就无法稳定该怎么办?
user606723

1
@ user606723,如果您的API不稳定,则它可能是不成熟的,或更可能是错误的抽象
sdg 2012年

@ user606723-是的,在这种情况下,命名约定本身应该定义某种形式的透明性,以隐藏实际的编程逻辑/细节..如果您必须修改实际的来源,则应通过信息性命名来表达更多的细节和想法。因此,从本质上讲,透明性可以通过适当的向下命名来实现,但是,我们实际上并不需要经常将其完全向下。
hanzolo 2012年

@sdg,或者因为系统要求总是在变化。因为法律改变了,因为别人的API改变了,所以这是我们无法控制的。
user606723,2012年

1
@ user606723-我实际上是在金融SaaS系统上工作,我看到每个发布周期没有足够抽象的陷阱。其中一些是由于设计不当造成的,但通常是由于将代码推到了原本不希望的地方而导致的。没有这些注释,名称和封装,沿链走下去可能会很痛苦。如果我要做的就是Remeasurements.GetRemeasureType(grant),然后是reMeasure.PerformCalc(),那将比添加重载并读取不同的逻辑分支来进行正确的计算或添加新的计算要好得多
hanzolo 2012年

1

您所说的“隐藏”,许多人将其视为关注点分离(例如,实现与接口)。

在我看来,抽象的一个主要好处是可以减少开发人员有限的大脑空间所造成的不必要细节的混乱。

如果混淆了实现代码,我会认为这阻碍了透明度,但是正如我所见,抽象只是一个很好的组织。


1

首先,除了一条机器代码指令外,任何东西本质上都是抽象的- while...do循环是一种一致的符号方式,用于表示重复和重复执行一组指令直到满足条件所需的比较和地址调用。同样,int类型是X个位数的抽象(取决于您的系统)。编程全部与抽象有关。

您可能会同意这些原始抽象很有用。好吧,因此能够建立自己的。OOAD和OOP都是关于。

假设您有一个要求,即用户希望能够以多种格式从屏幕上导出数据:定界文本,excel和pdf。您可以使用方法export(data)创建一个名为“ Exporter”的接口,这样可以方便地创建DelimitedTextExporter,ExcelExporter和PDFExporter,每个接口都知道如何创建特定的输出,这不是很方便吗?所有调用程序需要知道的是,它可以调用export(data)方法,无论使用哪种实现都可以完成其工作。此外,如果定界文本规则发生变化,则可以更改DelimitedTextExporter,而不必弄乱ExcelExporter,也可以破坏它。

面向对象编程中使用的几乎所有众所周知的设计模式几乎都依赖于抽象。我建议阅读Freeman和Freeman的Head First Design Patterns,以更好地理解为什么抽象是一件好事


1
即使机器代码是一种抽象,那里有真正的,物理的,过程正在进行的逻辑之下,甚至数码电子是在什么抽象真的发生,因为任何人谁取得了超频芯片可以作证的哈希
JK。

太正确了,尽管它在机器指令级别上似乎更为具体。
马修·弗林

1

我想我理解您对此的感觉,并且我也有类似的看法。

我与Java开发人员合作,他们将50行代码行类转换为3个类和3个接口,因为它很容易理解。我受不了了。

这件事很难理解,几乎不可能调试,也永远不需要“切换实现”。

另一方面,我还看到了多个对象共享相似行为并在一个地方使用的代码,如果方法可以通过公共接口公开,则它们实际上可以使用公共的排序/处理循环。

因此,恕我直言,可能在相似场景中使用的核心对象通常会受益于应该通过接口访问的常见行为。但这差不多就可以了,因为它是对的,所以可以抽象一些简单的东西,或者可以切换实现只是使代码混乱的一种方式。

再说一遍,与所有存在生命周期管理问题,难以理解的关系和意大利面条调用图的爆炸性小类相比,我更喜欢更长的智慧类。所以有些人会不同意我。


1

隐藏和抽象的指导目的应该是使用户与实现脱钩,以便他们可以独立更改。如果消费者与实现细节结合在一起,由于对内部的不满,两者都是一成不变的,因此引入新功能变得更加困难或将来更好的算法。

在编写模块时,实现的隐藏部分使您能够进行更改,而不必担心会破坏您无法想到的其他代码。

提供不透明界面的另一个优点是它们显着减小了子系统之间的表面积。通过减少它们交互的方式,它们可以变得更加可预测,更易于测试并且漏洞更少。模块之间的交互也随着模块数量的增加而平方增加,因此尝试控制这种复杂性的增长是有价值的。


也就是说,当然有可能隐藏太多并且将接口嵌套得太深。作为一个有才华的人,程序员的工作是对系统进行设计,以使其在最大程度上有用,同时还最小化复杂性并最大化可维护性。


0

在很多情况下,您根本不需要知道如何实现。我几乎可以保证您people.Where(p => p.Surname == "Smith")每天会写如此多次这样的代码,但您几乎不会想到“这种Where()方法实际上是如何工作的?” 您只是不在乎-您知道该方法存在,并且可以为您带来所需的结果。您为什么要关心它的工作原理?

这对于任何内部软件都是完全相同的;仅仅因为它不是由Oracle,Microsoft等编写的,并不意味着您应该去研究它的实现方式。您可以合理地期望使用一种称为的方法GetAllPeopleWithSurname(string name)来返回具有该姓氏的人员列表。它可能会遍历一个列表,可能会使用一个字典,可能会做一些完全疯狂的事情,但是您根本不在乎

当然,此规则有一个例外(没有一个规则就不会成为规则!),即方法中是否有错误。因此,在上面的示例中,如果您有一个包含3个人的列表,并且您知道其中一个人的姓氏为Smith,并且列表中没有返回他们,您需要考虑该方法的实现,因为它显然已损坏。

正确完成抽象后,它是很棒的,因为它使您可以过滤掉所有以后需要阅读时无用的内容。别忘了花在阅读代码上的时间比在编写代码上花费的时间多,因此重点必须放在使该任务尽可能容易上。您可能还认为,抽象意味着对象的层次结构与您的手臂一样长,但是它可以简单地将100行方法重构为10种方法,每方法长10行。因此,以前将10个步骤全部捆绑在一起的东西现在变成了10个独立的步骤,使您可以直接转到隐藏此讨厌的bug的地方。


嗯,但是可以说您是维护实施的人。我的意思是,有人在乎实施,而那个人就是您。您也恰好是实现的用户...业务需求变更(以您无法预测的方式)导致了多层变更。我想我的意思是,错误不是该规则的唯一例外。
user606723,2012年

问题是PeopleFactory.People.Strategy.MakePeople.(CoutryLaw.NameRegistry.NameMaker.Make()) as People.Female
编码器

@ user606723您不必一直在乎代码库中的每一行代码。如果该实现中存在错误,或者被标记为需要重写(因为它很慢,写得不好或其他原因)而您正在重写,那么您就在意它。否则,应远离它。也许您的问题是您试图一直拥有所有代码。我认为您应该只专注于特定时间正在处理的代码。
Stuart Leyland-Cole

@Coder显然,这是一种极端的抽象形式!起初,我认为这是OP所反对的事情,但是从阅读其余答复之后,似乎他们认为所有抽象都是不好的。这就是为什么我试图在答案中解释不同级别的抽象的原因。
Stuart Leyland-Cole

0

抽象导致信息隐藏。这应该以较低的耦合度结束。这将导致更少的变更风险。这应该使程序员感到快乐,而在接触代码时不会感到紧张。

这些想法通过软件体系结构中的三个基本定律表达:

西蒙定律:“等级制降低了复杂性”。(层次结构引入抽象)

帕尔纳定律:“只有隐藏的内容才能无风险地更改。”

康斯坦丁定律:健壮的程序需要低耦合和高凝聚力


“层次结构降低了复杂性。” -不一定正确。
编码员

没有设计方法是确定性的。该定律并非来自IT / CS,而是从广义上制定的,并且也被数学,物理学家等引用。这是一个有效的原理,但是没有人可以阻止您创建无缺陷的层次结构。
ins0m 2012年

0

我也从事企业应用程序业务,这个问题引起了我的注意,因为我自己也有同样的问题。到目前为止,我在整个职业生涯中都对抽象问题有一些见解,但我的见解绝不是通用答案。我将继续学习/聆听新的想法和思想,因此我认为现在可能会改变。

当我维护大型复杂的医疗应用程序时,就像您一样,我讨厌那里的所有抽象。弄清楚所有代码的去向是脖子痛。跳来跳去让我头晕目眩。因此,我对自己说:“抽象很烂,设计东西时我会尽量减少抽象”。

然后,到了我必须重新设计一个应用程序(相对较小的Web服务组件)的时候。记住所有的痛苦,我对该组件进行了漂亮的平面设计。问题是,当需求改变时,我不得不在许多不同的地方进行改变(需求非常多,而我对此无能为力)。真是太糟糕了,我基本上放弃了最初的设计,然后进行了抽象化的重新设计,情况变得更好了-当需求发生变化时,我不必在很多地方进行更改。

我提交了应用程序,坐了几周,然后被告知开始维护该应用程序。我已经有一段时间了,我什么都不记得了,所以我在理解我自己的代码上有些挣扎,抽象也无济于事。

之后,我参加了许多其他不同的项目,并有机会尝试更多的抽象级别。我真正发现的是,这只是我个人的观点,抽象对开发有很大帮助,但是当您不编写代码并试图从应用程序的最深层次理解所有内容时,它会带来负面影响。您将花费更多的时间在不同的班级上跳来跳去,并尝试建立联系。

我的感觉是,抽象在开发期间非常有价值,以至于我们在尝试理解代码时作为维护人员遇到的麻烦值得。存在解决业务问题的软件,业务问题随着时间而发展;因此,软件必须随着时间而发展。没有抽象,就很难开发软件。可以以一种方式设计抽象,以便维护人员一旦看到代码结构的模式,便可以轻松地在代码库中导航,因此只有最初的学习曲线令人沮丧。


0

就像其他人所说的那样,抽象背后的“隐藏细节”使它们可以更改而不会影响用户。这个想法来自Parnas的“ 将系统分解为模块的标准”(1972年),并且与抽象数据类型(ADT)和面向对象编程的想法有关。

大约在同一时间,Codd的大型共享数据库的数据关系模型(1970)受到了启发(请参见摘要和介绍),因为它希望更改数据库的内部存储表示形式,而不影响数据库的用户。他看到程序员经常花几天时间修改代码页,以应对较小的存储更改。

也就是说,如果您必须查看其中的内容才能使用它,那么抽象不是很有用。设计良好可能非常困难。良好抽象的一个例子是加法-您最后一次是什么时候考虑内部发生了什么?(但是有时您会这样做,例如为了溢出)。

根本问题(IMHO)是,要正确设计模块(就Parnas而言),您需要预测会发生什么变化以及不会发生什么变化。预测未来是困难的-但是,如果您对某件事有很多经验并且清楚地了解它,那么您可以做得很好。因此,您可以设计一个运行良好的模块(抽象)。

但是,似乎所有抽象的命运-甚至是最好的-最终都会发生需要打破抽象的无法预料的(并且可以说是无法预料的)变化。为了解决这个问题,有些抽象有一个转义,如果您确实需要它,您可以进入更深的层次。

这一切似乎都是负面的。但是我认为事实是,我们被工作良好的抽象所包围,以至于我们没有注意到它们,也没有意识到它们隐藏的内容。我们只注意到较差的抽象,因此对它们有黄疸的看法。


0

抽象主要是为了其消费者(例如,应用程序程序员)的利益。系统(设计师)程序员需要做更多的工作才能使它们美观和有用,这就是为什么初学者通常不会进行好的设计。

也许您不喜欢抽象,因为它们总是会增加复杂性?您使用的系统是否可能患有抽象性炎(过度使用抽象性)?他们不是万能药。

有用的抽象的额外工作和复杂性应该得到回报,但是很难确定。如果您将抽象视为关键点,则软件设计可以在任一侧灵活进行:可以在不破坏客户代码的情况下修改抽象的实现,和/或新客户可以轻松地重新使用抽象来制造新事物。

通过证明抽象在一段时间内朝着以下一个或两个方向“屈服”,您几乎可以衡量它们的投资回报:相对轻松的实现更改和其他新客户。

例如:使用Java中的Socket类的抽象,我确定Java 1.2中的应用程序代码在Java 7下仍然可以正常工作(尽管它们可能会在性能上有所变化)。从Java 1.2开始,肯定有很多新的客户端都使用了这种抽象。

至于对抽象的不满意,如果我与维护Socket类背后代码的开发人员进行了交谈,那么他们的生活可能不像使用Socket编写有趣的应用程序的客户那样繁琐和乐观。当然,实现抽象的工作肯定比使用抽象的工作多。但这并没有使它变糟。

至于透明度,在自上而下的设计策略中,总的透明度会导致不良的设计。聪明的程序员往往会充分利用自己掌握的信息,然后系统紧密耦合。模块中细节的最小更改(例如,更改数据结构中的字节顺序)可能会破坏其他地方的某些代码,因为聪明的程序员使用该信息来做一些有用的事情。大卫·帕纳斯(David Parnas)在1971年的文章中指出了这个问题,他提出在设计中隐藏信息。

如果您将操作系统的“内部”视为抽象的复杂实现(即运行于其上的应用程序的操作系统),则对ArchLinux的引用对我来说很有意义。在抽象中保持简单。


0

我会用一个问题回答你的问题;当您今天早上开车去工作时(我假设您实际上已经这样做了),您是否真的在乎引擎如何打开气门以引入燃油-空气混合物,然后点燃它们?不,你不关心如何您的汽车引擎,当你驾驶的道路工程。您关心它是否有效。

假设有一天,您的汽车无法工作。没有开始,扔了一根杆,断裂了一条皮带,在您忙着发短信时,莫名其妙地犁入了那个混凝土障碍,没有您自己的过错。现在,您需要一辆新车(至少是暂时的)。您是否在乎这款新车的工作原理?不。您关心的是,它首先起作用,其次,您可以使用驾驶旧车时所使用的相同知识和技能来驾驶新车。理想情况下,您应该会发现自己驾驶的汽车没有任何变化。实际上,这种新车的工作方式应该给您尽可能少的“惊喜”。

这些基本原则是封装和抽象背后的核心原则。了解对象如何执行其操作不应成为使用该对象执行其操作的必要条件。即使在计算机编程中,运行程序的CPU内电气路径的详细信息也至少要在六层I / O指令,驱动程序,OS软件和运行时之后才抽象出来。许多非常成功的软件工程师编写了完美的代码,而不必担心会运行该代码的确切硬件体系结构或操作系统构建。包括我。

封装/信息隐藏允许“不在乎它如何工作,在乎它在做什么”的思想。您的对象应该以对消费者有用的方式公开对消费者有用的东西。现在,回到现实世界中,这并不意味着汽车不应该向用户提供任何有关内部工作的信息,也不意味着汽车仅应向用户提供点火,方向盘,和踏板。所有汽车都有速度表和燃油表,转速表,白痴灯和其他反馈。实际上,所有汽车还具有用于各种独立子系统的开关,例如前大灯,转向信号灯,收音机,座椅调节装置等。一些汽车允许用户进行一些深奥的输入,例如限滑中央差速器的灵敏度。在所有情况下,如果您足够了解,您可以打开它并进行更改以使其工作方式略有不同。但是,在大多数情况下,也许,也许也许,用户不应该能够直接和独立地从机舱内部控制燃油泵吗?也许,也许,用户不应该踩下制动踏板就不能激活他们的制动灯?

抽象允许“这并不相同,但是因为它们都是XI可以像使用任何X一样使用它们”的思想。如果您的对象继承或实现了抽象,那么您的使用者应该期望您的实现产生与其他已知的抽象实现相同或相似的结果。丰田凯美瑞和福特Fusion都是“汽车”。因此,它们具有一组通用的预期功能,例如方向盘。逆时针旋转,汽车向左行驶。顺时针旋转,汽车向右行驶。您可以在美国乘坐任何汽车,并期望该汽车具有方向盘和至少两个踏板,右侧的一个是“汽车行驶”踏板,而中间的一个是“汽车停靠”踏板。

抽象的必然结果是“最少惊讶的理论”。如果您开着一辆新车试驾,顺时针旋转方向盘而汽车向左转,至少可以说,您会感到惊讶。您指责交易商兜售POS,并且不太可能听到他的任何原因,说明新行为“比您惯用的”要“好”,或者这种行为被“证明”的程度如何,或者“透明”的控制系统。尽管有这辆新车,但您驾驶的其他所有车仍然是“汽车”,在驾驶这辆车时,您必须更改一些有关如何驾驶汽车的基本概念,以便成功驾驶新车。这通常是一件坏事,而且只有在新范例具有直观优势的情况下才会发生。也许增加安全带就是一个​​很好的例子。50年前,您只是进进出出,但是现在您必须系好安全带,直觉上的好处是,如果发生事故,您不会穿过挡风玻璃或进入乘客座椅。即便如此,驾驶员还是拒绝了。许多车主将安全带从汽车上拉下,直到通过法律强制使用安全带。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.