抽象和封装的区别是什么?

451

封装和抽象有什么精确的区别?


请检查我的答案。你会对这些概念有一个不同的看法。https://stackoverflow.com/a/65543358/12894468 - undefined
41个回答

316

这里大部分答案都是关注面向对象编程(OOP),但是封装其实早在此之前就开始了:

  • Every function is an encapsulation; in pseudocode:

    point x = { 1, 4 }
    point y = { 23, 42 }
    
    numeric d = distance(x, y)
    

    Here, distance encapsulates the calculation of the (Euclidean) distance between two points in a plane: it hides implementation details. This is encapsulation, pure and simple.

  • Abstraction is the process of generalisation: taking a concrete implementation and making it applicable to different, albeit somewhat related, types of data. The classical example of abstraction is C’s qsort function to sort data:

    The thing about qsort is that it doesn't care about the data it sorts — in fact, it doesn’t know what data it sorts. Rather, its input type is a typeless pointer (void*) which is just C’s way of saying “I don't care about the type of data” (this is also called type erasure). The important point is that the implementation of qsort always stays the same, regardless of data type. The only thing that has to change is the compare function, which differs from data type to data type. qsort therefore expects the user to provide said compare function as a function argument.

封装和抽象是密不可分的,以至于你可以说它们真正地不可分割。从实际角度来看,这可能是真的;话虽如此,这里有一个封装并不是很抽象的例子:

class point {
    numeric x
    numeric y
}

我们封装了点的坐标,但并没有从物质上将它们抽象出来,仅仅是在逻辑上进行了分组。
下面是不是封装而是抽象的一个例子:
T pi<T> = 3.1415926535

这是一个具有给定值(π)的通用变量pi,声明不关心变量的确切类型。诚然,在实际代码中很难找到像这样的东西:抽象几乎总是使用封装。然而,上述内容在C++(14)中实际存在,通过变量模板(= 变量的通用模板);稍微复杂一些的语法,例如:

template <typename T> constexpr T pi = T{3.1415926535};

38
每种方法都是一种封装,但也是一种抽象,因为每次你将一些东西放在一起并为其命名时,你就创建了一个新的(抽象)概念。没有抽象的封装是无用的。因此,他们之间没有任何共同点这种说法是不正确的。 - proskor
@proskor 我认为,即使它们的领域重叠,这些概念仍然是正交的。甚至可能每个封装都是一种抽象(虽然我并不完全相信)-但即使如此,我认为这将是偶然的而不是任何一个概念固有的属性。 - Konrad Rudolph
11
它们是不同的,但并非相互独立的。事实上,我认为封装确实是一种特殊类型的抽象,即结构性抽象。通过将复杂物体作为一个整体来考虑,我们基本上忽略(从抽象出)它是由其它东西建造而成的详细信息,也就是忽略了它的内部结构。 - proskor
抽象化是指我们将实现级别的细节隐藏起来,仅向用户提供访问必要值的方式,例如Sum(1,10)将对其进行求和。我们不知道如何做到这一点。我们已经从用户那里抽象出了求和的过程。而封装的标准定义建议我们封装即使一个数据和作用于它的方法。就像类一样。将它们绑定成一个单一实体。 我想在这里强调的是,没有什么是绝对的,两者需要相互存在。没有其中之一就不会有另一个。 - Saras Arya
5
我猜测“为什么”被跳过了,因为它似乎太琐碎了:没有抽象和封装,我们无法编写复杂的系统。即使是较为简单的问题也需要如此复杂的程序代码,以至于从一开始就会失败。没有抽象,甚至不能编写一个打印数字的程序:概念“打印”涉及无数的抽象(什么是屏幕?什么是字符?什么是像素?...) - Konrad Rudolph
显示剩余4条评论

228

很多答案及其示例都是误导性的。

封装是将“数据”和“对该数据进行操作的函数”打包到单个组件中,并限制对对象组件的访问。
封装意味着对象的内部表示通常对对象定义以外的视图隐藏。

抽象是一种机制,可以表示基本特征而不包含实现细节。

封装:--信息隐藏
抽象:--实现隐藏

示例(使用C++语言):

class foo{
    private:
        int a, b;
    public:
        foo(int x=0, int y=0): a(x), b(y) {}

        int add(){    
            return a+b;   
        } 
}  

foo类的任何对象的内部表示都被隐藏在该类之外。 --> 封装
foo对象的任何可访问成员(数据/函数)都受到限制,只能由该对象访问。

foo foo_obj(3, 4);
int sum = foo_obj.add();

方法add的实现已被隐藏。--> 抽象化.


1
@bjan; 在C中使用qsort函数是抽象的一个例子。你不知道它的具体实现细节。这里没有涉及封装。 在C++中使用构造函数初始化对象的数据字段是封装的一个例子(通过构造函数控制访问对象的组件)。 - haccks
1
@ArunRaaj;从正在使用它的其他对象中移除。 - haccks
2
这个答案应该被标记为正确答案,只需要进行一些小的编辑。 - Harshit Jindal
9
这应该是最好的答案。简单明了,附有简单的例子。 - Don Dilanga
@AdilH.Raza 为什么应该是那样的呢? - haccks
显示剩余5条评论

149

封装是隐藏可能是通用或专门行为的实现细节。

抽象提供了一种概括(例如,对一组行为进行概括)。

这是一篇不错的阅读材料:《抽象、封装和信息隐藏》,作者是 Object Agency 的 Edward V. Berard。


6
尼哈的链接现在也失效了,但没关系,我们可以通过谷歌搜索文章名称。我偶然发现了这篇文章:http://www.tonymarston.co.uk/php-mysql/abstraction.txt。 - Abhijeet Apsunde
2
让我明白了的引言:“通常,抽象并非以信息隐藏为定义,例如请注意使用“忽略”和“提取”等词语。然而,我们还应该注意到上述示例中出现了“抑制”和“压制”等词语的使用。简而言之,可以说抽象规定了某些信息比其他信息更重要,但(正确地)没有指定处理不重要信息的具体机制。” - chtenb
5
面向对象编程中的封装并不是指隐藏某些内容,而是将状态和行为结合在一起以保护不变量。 - Eugene Khudoy

116

封装将一些东西放在盒子里并给你一个窥视孔,这样就可以防止你对齿轮等东西进行破坏。

抽象直接忽略那些不重要的细节,比如这些东西是否有齿轮、棘轮、飞轮或核心;它们只是"运行"。

封装的例子:

  • 内衣裤
  • 工具箱
  • 钱包
  • 手提包
  • 胶囊
  • 冷冻碳化物
  • 一个箱子,带或不带按钮
  • 卷饼(严格来说,卷饼中的玉米面薄饼)

抽象的例子:

  • “一组事物”是一种抽象(我们称之为聚合)
  • “包含其他事物的事物”是一种抽象(我们称之为组合)
  • "容器" 是另一种 "包含其他事物的事物" 抽象;注意,所有的封装示例都是容器的一种,但并非所有容器都具有/提供封装。例如,篮子是一种不封装其内容的容器。

12
为什么这个被踩了?在这片错误答案的汪洋中,这是唯一正确的描述之一。 - Konrad Rudolph
4
仅提供窥视孔进行封装,我们是否忽略了对用户无关紧要的细节-这就是所谓的抽象[你所说的]。这两件事情有什么不同?也许你可以再详细解释一下。 - Sanjeev Kumar Dangi
67
@Sanjeev 封装是具体的,抽象是抽象的!;-)封装是你可以使用的对象,抽象是你只能讨论的理想。封装是你穿内衣裤的原因,抽象是你解释内衣和泳衣之间区别的方式。 - Steven A. Lowe
6
抽象是我们作为思考者处理复杂性的方式:我们忽略无关的细节,强调相关的共同模式,使用符号代替实际对象,并通过它们的行为来表征相似的事物(除了其他抽象模式)。抽象不是计算机科学发明的,它是古老的——象形文字是抽象的,单词是抽象的,思想是抽象的。封装同样古老(内衣、盔甲、盒子)。你可能试图使这些概念比它们实际上更难理解。 - Steven A. Lowe
1
@bjan:我并没有说它们不是容器;它们确实是。我添加了一些不太明显的容器,但“封装”的概念中隐含了“包含”。 “容器”是一个抽象概念。篮子是一个容器,但它不能完全封装(覆盖、隐藏、保护)其内容。这有帮助吗? - Steven A. Lowe
显示剩余6条评论

77

封装 意味着使用getter和setter等方式隐藏数据。

抽象 意味着使用抽象类和接口等方式隐藏实现细节。


你的回答与“抽象是泛化的过程”有何关联? - 我们如何借助抽象类和接口实现泛化。你有任何例子吗? - N Sharma
好的答案。IEAP - Billu

67

抽象是一个广义术语。即封装是抽象的子集。

抽象 封装
解决设计层面上的问题。 封装解决实现层面上的问题。
隐藏不必要的细节,展示必要的信息。 将代码和数据隐藏到单个实体或单元中,以保护数据免受外部世界的影响。
关注外部视图。 关注内部工作。
让我们专注于对象的功能,而不是它如何实现功能。 让我们专注于对象如何执行某些操作。
例如:手机的外观,比如它有一个显示屏和按钮。 例如:手机的内部细节,按钮和显示屏如何使用电路相连接。

例子:解决方案架构师是创建整个解决方案的高层抽象技术设计的人,然后将该设计交给开发团队进行实现。在这里,解决方案架构师充当抽象,开发团队充当封装。
例子:用户数据的封装(网络)。

enter image description here

图片来源

抽象(或模块化)- 类型使程序员能够以比特或字节更高的级别思考,不必关注低级实现。例如,程序员可以开始将字符串视为一组字符值,而不仅仅是字节数组。更高级别的类型使程序员能够考虑和表达两个任意大小子系统之间的接口。这使得本地化的级别更多,因此在这两个子系统通信时,所需的定义保持一致,以实现子系统的互操作性。 来源

Java示例


这是所有其他答案中“最简单”的解释,也应该被接受为正确答案。 - vikramvi

51

以上提供了很多好的答案,但我将在这里提出我的(Java)观点。

数据封装简单地说就是将逻辑上分组的数据包装起来并控制其访问权限。它通常与另一个关键词数据隐藏相关联。在Java中,可以使用访问修饰符来实现。

一个简单的例子是定义一个私有变量,并使用getter和setter方法给予访问权限,或者将一个方法设置为私有方法,因为它只在类内部使用。用户不需要知道这些方法和变量。

注意: 不应误解封装只是关于数据隐藏。当我们说封装时,重点应该放在将相关的数据和行为进行分组、打包或捆绑在一起。

数据抽象是将复杂逻辑隐藏起来,以便用户不会看到底层的复杂性。在Java中,可以通过使用接口抽象类来实现这一点。

例子 -

假设我们有一个名为Animal的接口,它有一个makeSound()函数。有两个具体类Dog和Cat实现了这个接口。这些具体类有不同的makeSound()函数实现。现在假设我们有一个动物(我们从某个外部模块中得到)。所有用户知道的是它接收到的对象是一个Animal,并且用户有责任打印出动物的声音。一种暴力的方法是检查接收到的对象以识别其类型,然后将其强制转换为该Animal类型,然后调用makeSound()函数。但更简洁的方法是将事情抽象出来。使用Animal作为多态引用并在其上调用makeSound()函数。在运行时,根据真实的对象类型调用适当的函数。
更多细节请看这里

enter image description here

复杂的逻辑在电路板中封装在触摸板中,提供了一个漂亮的界面(按钮)来将其抽象化为用户可用。
PS:上述链接是我的个人博客。

2
你的博客太棒了!!我现在完全理解了所有面向对象编程的概念,并且能够应用到实践中! - minigeek
2
到目前为止最佳答案。谢谢。 - Mario Levesque
在数据抽象中,您说“底层复杂逻辑不会暴露给用户”,我有一个问题:1.谁是用户,为什么要对用户隐藏? - Abhinay Singh Negi

40

这些都是在计算机科学和编程中不是唯一的模糊概念。我想提供一些额外的思考,以帮助其他人理解这些重要的概念。


简短回答

封装 - 隐藏和/或限制系统某些部分的访问,同时公开必要的接口。

抽象 - 考虑去除某些特征的事物,与具体的现实、具体对象或实际实例相分离,从而减少复杂性。

主要的相似之处是这些技术旨在提高理解和效用

主要的区别抽象是表示事物更简单的一种方式(通常是为了使表示更广泛适用),而封装是改变其他事物与某个东西交互的方法。


详细回答

封装

以下是一个封装的示例,希望能更清楚地说明问题:

Arduino Encapsulation

这里我们有一个Arduino Uno,以及一个被包装在外壳内的Arduino Uno。外壳是封装的一个很好的表示。

封装旨在保护某些组件免受外部影响和知识,以及公开其他东西应该接口的组件。在编程术语中,这涉及通过信息隐藏访问修饰符改变某些变量和/或属性可以读取和写入的程度。

然而,封装还旨在更有效地提供这些外部接口。以我们的Arduino示例为例,这可能包括漂亮的按钮和屏幕,这使得用户与设备的交互变得更加简单。它们向用户提供了影响设备行为和获取有用信息的简单方法,否则这将更加困难。

在编程中,这涉及将各种组件分组为可分离结构,例如函数对象。它还包括提供与这些结构进行交互的手段,以及获取有关它们的有用信息的方法。

封装可以帮助程序员在许多方面获益,其中最重要的是改进代码可维护性和可测试性。

抽象

虽然许多其他答案将抽象定义为概括,但我个人认为这种定义是错误的。我会说概括实际上是一种特定的抽象类型,而不是另一种方式。换句话说,所有概括都是抽象,但并非所有抽象都是概括。

以下是我对抽象的理解:

Pixel Tree

你会说那里的图像是一棵树吗?很可能是。但它真的是一棵树吗?当然不是!它只是一堆像我们可能称之为树的东西。我们可以说它代表了真实树的一个抽象。请注意,该树的几个视觉细节被省略了。此外,它不会生长、消耗水或产生氧气。怎么可能呢?它只是屏幕上的一堆颜色,由计算机内存中的字节表示。

这就是抽象的本质所在。它是简化事物以便于理解的一种方式。你脑海中的每个想法都是现实的一种抽象。你对树的心理形象不会比这个jpeg更成为实际存在的树。

在编程中,我们可以通过创建一个Tree类并为其添加模拟生长、耗水和产氧的方法来利用抽象化。我们创建的这个类将代表我们对真实树木的经验,并且只包含我们特定模拟所关心的元素。我们使用抽象化来通过字节和数学的方式表示我们对某些事物的经验。

抽象类

编程中的抽象化也允许我们考虑几个“具体”对象类型(实际存在的类型)之间的共同点,并在一个独特的实体内定义这些共同点。例如,我们的Tree类可能继承自一个abstract class Plant,这个类有一些属性和方法适用于所有像植物的类,但去掉了每种植物特有的部分。这可以显著减少代码的重复性并提高可维护性。

abstract class和普通的class的实际区别在于,在概念上不存在abstract class的“真实”实例。构建Plant对象是没有意义的,因为那不够具体。每个“真实”的Plant也都是更具体类型的Plant

此外,如果我们希望程序更加真实,我们可能需要考虑到我们的Tree类本身可能太抽象。在现实中,每棵Tree都是更具体类型的Tree,因此我们可以为这些类型创建类,例如BirchMaple等,它们继承自我们现在可能是abstractTree类。

JVM

另一个良好的抽象实例是Java虚拟机(JVM),它为Java代码提供了一个虚拟或抽象计算机来运行。它基本上取走了系统特定组件,并提供了一个抽象接口“计算机”,而不考虑任何特定系统。

区别

封装与抽象不同之处在于它与某事物的“真实”或“准确”程度无关。它不会删除某些组件以使其更简单或更广泛适用。相反,它可能隐藏某些组件以实现类似的目的。


29
  • 抽象让你专注于对象的功能,而不是它如何实现该功能
  • 封装意味着隐藏对象执行某些操作的内部细节或机制。

就像开车一样,你知道油门踏板的作用,但可能不清楚背后的过程,因为它是封装的。

让我在C#中举个例子。假设你有一个整数:

int Number = 5;
string aStrNumber = Number.ToString();

您可以使用类似于 Number.ToString() 的方法,它会返回数字 5 的字符表示,并将其存储在字符串对象中。该方法告诉您它要做什么,而不是如何做。


抽象化可以在使用的语言或范例无关的情况下进行构建。简短回答,是的,C语言中可以有抽象化。为什么不呢? - jasonco
21
你对抽象和封装的定义是相同的。据我理解,抽象是隐藏了如何完成某件事情,仅展示它完成了什么;而封装则是将数据和方法包装在对象内部,以保护其不受外部干扰。关于汽车和Number.ToString()的例子,你能否具体指出哪些是抽象和封装呢?这有助于澄清问题。 - Sanjeev Kumar Dangi
在我看来,信息隐藏就是你所说的封装。你可以参考这个答案中的链接或者Java中的封装 - Not a bug
@nisargshah95 是的,就像我一样,我提供了链接来区分信息隐藏和封装。(PS:我是那篇文章的作者)。在我的评论中提到,这是信息隐藏而不是封装。 - Not a bug
@KisHanSarsecHaGajjar 对不起,我误读了你的评论。我撤回我的评论 :) - nsane
显示剩余4条评论

22

封装:是将不想让实际用户了解的、意料之外的或专有的实现细节隐藏起来的过程。

List<string> list = new List<string>();
list.Sort(); /* Here, which sorting algorithm is used and hows its 
implemented is not useful to the user who wants to perform sort, that's 
why its hidden from the user of list. */

抽象: 是一种提供概括和处理各种对象的常见方式。例如:

class Aeroplane : IFlyable, IFuelable, IMachine
{ // Aeroplane's Design says:
  // Aeroplane is a flying object
  // Aeroplane can be fueled
  // Aeroplane is a Machine
}
// But the code related to Pilot, or Driver of Aeroplane is not bothered 
// about Machine or Fuel. Hence,
// pilot code:
IFlyable flyingObj = new Aeroplane();
flyingObj.Fly();
// fighter Pilot related code
IFlyable flyingObj2 = new FighterAeroplane();
flyingObj2.Fly();
// UFO related code 
IFlyable ufoObj = new UFO();
ufoObj.Fly();
// **All the 3 Above codes are genaralized using IFlyable,
// Interface Abstraction**
// Fly related code knows how to fly, irrespective of the type of 
// flying object they are.

// Similarly, Fuel related code:
// Fueling an Aeroplane
IFuelable fuelableObj = new Aeroplane();
fuelableObj.FillFuel();
// Fueling a Car
IFuelable fuelableObj2 = new Car(); // class Car : IFuelable { }
fuelableObj2.FillFuel();

// ** Fueling code does not need know what kind of vehicle it is, so far 
// as it can Fill Fuel**

5
你提供的封装示例实际上是抽象的示例。 - haccks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接