封装和抽象有什么精确的区别?
封装和抽象有什么精确的区别?
这里大部分答案都是关注面向对象编程(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};
很多答案及其示例都是误导性的。
封装是将“数据”和“对该数据进行操作的函数”打包到单个组件中,并限制对对象组件的访问。
封装意味着对象的内部表示通常对对象定义以外的视图隐藏。
抽象是一种机制,可以表示基本特征而不包含实现细节。
封装:--信息隐藏。
抽象:--实现隐藏。
示例(使用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
的实现已被隐藏。--> 抽象化.
qsort
函数是抽象的一个例子。你不知道它的具体实现细节。这里没有涉及封装。
在C++中使用构造函数初始化对象的数据字段是封装的一个例子(通过构造函数控制访问对象的组件)。 - haccks封装是隐藏可能是通用或专门行为的实现细节。
抽象提供了一种概括(例如,对一组行为进行概括)。
这是一篇不错的阅读材料:《抽象、封装和信息隐藏》,作者是 Object Agency 的 Edward V. Berard。
封装将一些东西放在盒子里并给你一个窥视孔,这样就可以防止你对齿轮等东西进行破坏。
抽象直接忽略那些不重要的细节,比如这些东西是否有齿轮、棘轮、飞轮或核心;它们只是"运行"。
封装的例子:
抽象的例子:
抽象是一个广义术语。即封装是抽象的子集。
抽象 | 封装 |
---|---|
解决设计层面上的问题。 | 封装解决实现层面上的问题。 |
隐藏不必要的细节,展示必要的信息。 | 将代码和数据隐藏到单个实体或单元中,以保护数据免受外部世界的影响。 |
关注外部视图。 | 关注内部工作。 |
让我们专注于对象的功能,而不是它如何实现功能。 | 让我们专注于对象如何执行某些操作。 |
例如:手机的外观,比如它有一个显示屏和按钮。 | 例如:手机的内部细节,按钮和显示屏如何使用电路相连接。 |
抽象(或模块化)- 类型使程序员能够以比特或字节更高的级别思考,不必关注低级实现。例如,程序员可以开始将字符串视为一组字符值,而不仅仅是字节数组。更高级别的类型使程序员能够考虑和表达两个任意大小子系统之间的接口。这使得本地化的级别更多,因此在这两个子系统通信时,所需的定义保持一致,以实现子系统的互操作性。 来源
以上提供了很多好的答案,但我将在这里提出我的(Java)观点。
数据封装简单地说就是将逻辑上分组的数据包装起来并控制其访问权限。它通常与另一个关键词数据隐藏相关联。在Java中,可以使用访问修饰符来实现。
一个简单的例子是定义一个私有变量,并使用getter和setter方法给予访问权限,或者将一个方法设置为私有方法,因为它只在类内部使用。用户不需要知道这些方法和变量。
注意: 不应误解封装只是关于数据隐藏。当我们说封装时,重点应该放在将相关的数据和行为进行分组、打包或捆绑在一起。
数据抽象是将复杂逻辑隐藏起来,以便用户不会看到底层的复杂性。在Java中,可以通过使用接口和抽象类来实现这一点。例子 -
假设我们有一个名为Animal的接口,它有一个makeSound()函数。有两个具体类Dog和Cat实现了这个接口。这些具体类有不同的makeSound()函数实现。现在假设我们有一个动物(我们从某个外部模块中得到)。所有用户知道的是它接收到的对象是一个Animal,并且用户有责任打印出动物的声音。一种暴力的方法是检查接收到的对象以识别其类型,然后将其强制转换为该Animal类型,然后调用makeSound()函数。但更简洁的方法是将事情抽象出来。使用Animal作为多态引用并在其上调用makeSound()函数。在运行时,根据真实的对象类型调用适当的函数。这些都是在计算机科学和编程中不是唯一的模糊概念。我想提供一些额外的思考,以帮助其他人理解这些重要的概念。
封装 - 隐藏和/或限制系统某些部分的访问,同时公开必要的接口。
抽象 - 考虑去除某些特征的事物,与具体的现实、具体对象或实际实例相分离,从而减少复杂性。
主要的相似之处是这些技术旨在提高理解和效用。
主要的区别是抽象是表示事物更简单的一种方式(通常是为了使表示更广泛适用),而封装是改变其他事物与某个东西交互的方法。
以下是一个封装的示例,希望能更清楚地说明问题:
这里我们有一个Arduino Uno,以及一个被包装在外壳内的Arduino Uno。外壳是封装的一个很好的表示。
封装旨在保护某些组件免受外部影响和知识,以及公开其他东西应该接口的组件。在编程术语中,这涉及通过信息隐藏和访问修饰符改变某些变量和/或属性可以读取和写入的程度。
然而,封装还旨在更有效地提供这些外部接口。以我们的Arduino示例为例,这可能包括漂亮的按钮和屏幕,这使得用户与设备的交互变得更加简单。它们向用户提供了影响设备行为和获取有用信息的简单方法,否则这将更加困难。
在编程中,这涉及将各种组件分组为可分离结构,例如函数
、类
或对象
。它还包括提供与这些结构进行交互的手段,以及获取有关它们的有用信息的方法。
封装可以帮助程序员在许多方面获益,其中最重要的是改进代码可维护性和可测试性。
虽然许多其他答案将抽象定义为概括,但我个人认为这种定义是错误的。我会说概括实际上是一种特定的抽象类型,而不是另一种方式。换句话说,所有概括都是抽象,但并非所有抽象都是概括。
以下是我对抽象的理解:
你会说那里的图像是一棵树吗?很可能是。但它真的是一棵树吗?当然不是!它只是一堆像我们可能称之为树的东西。我们可以说它代表了真实树的一个抽象。请注意,该树的几个视觉细节被省略了。此外,它不会生长、消耗水或产生氧气。怎么可能呢?它只是屏幕上的一堆颜色,由计算机内存中的字节表示。
这就是抽象的本质所在。它是简化事物以便于理解的一种方式。你脑海中的每个想法都是现实的一种抽象。你对树的心理形象不会比这个jpeg更成为实际存在的树。
在编程中,我们可以通过创建一个Tree
类并为其添加模拟生长、耗水和产氧的方法来利用抽象化。我们创建的这个类将代表我们对真实树木的经验,并且只包含我们特定模拟所关心的元素。我们使用抽象化来通过字节和数学的方式表示我们对某些事物的经验。
编程中的抽象化也允许我们考虑几个“具体”对象类型(实际存在的类型)之间的共同点,并在一个独特的实体内定义这些共同点。例如,我们的Tree
类可能继承自一个abstract class Plant
,这个类有一些属性和方法适用于所有像植物的类,但去掉了每种植物特有的部分。这可以显著减少代码的重复性并提高可维护性。
abstract class
和普通的class
的实际区别在于,在概念上不存在abstract class
的“真实”实例。构建Plant
对象是没有意义的,因为那不够具体。每个“真实”的Plant
也都是更具体类型的Plant
。
此外,如果我们希望程序更加真实,我们可能需要考虑到我们的Tree
类本身可能太抽象。在现实中,每棵Tree
都是更具体类型的Tree
,因此我们可以为这些类型创建类,例如Birch
、Maple
等,它们继承自我们现在可能是abstract
的Tree
类。
另一个良好的抽象实例是Java虚拟机(JVM),它为Java代码提供了一个虚拟或抽象计算机来运行。它基本上取走了系统特定组件,并提供了一个抽象接口“计算机”,而不考虑任何特定系统。
封装与抽象不同之处在于它与某事物的“真实”或“准确”程度无关。它不会删除某些组件以使其更简单或更广泛适用。相反,它可能隐藏某些组件以实现类似的目的。
就像开车一样,你知道油门踏板的作用,但可能不清楚背后的过程,因为它是封装的。
让我在C#中举个例子。假设你有一个整数:
int Number = 5;
string aStrNumber = Number.ToString();
您可以使用类似于 Number.ToString() 的方法,它会返回数字 5 的字符表示,并将其存储在字符串对象中。该方法告诉您它要做什么,而不是如何做。
封装:是将不想让实际用户了解的、意料之外的或专有的实现细节隐藏起来的过程。
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**