【问题标题】:What's the difference between abstraction and encapsulation?抽象和封装有什么区别?
【发布时间】:2014-09-21 15:51:19
【问题描述】:

在采访中,我被要求解释抽象和封装之间的区别。我的回答是这样的

  • 抽象允许我们以最简单的方式表示复杂的现实世界。它是识别对象应具备的相关品质和行为的过程;换句话说,代表必要的特征而不代表背景细节。

  • 封装是一个将对象的所有内部细节隐藏在外部现实世界之外的过程。 “封装”这个词,就像“封闭”成一个“胶囊”。它限制客户端查看实现抽象行为的内部视图。

我认为上面的回答让面试官信服了,但后来有人问我,如果两者的目的都是隐藏,那么为什么需要使用封装。当时我对此并没有很好的答案。

我应该添加什么以使我的答案更完整?

【问题讨论】:

  • 这是对这个问题的准确答案,stackoverflow.com/questions/742341/…
  • 我曾经回答过同样的问题:“封装更多是对象分类的概念(至少实际上),而抽象在大多数情况下是方法和函数的属性。所以两者都适用于不同的OOP 家族的成员。”

标签: oop encapsulation abstraction


【解决方案1】:

抽象与分离接口和实现有关。 (我们不关心它是什么,我们关心的是它以某种方式工作。

封装与禁止访问或了解实现的内部结构有关。 (我们不关心或需要了解 它是如何工作的,只关心它是如何工作的。)

有些人确实使用封装作为抽象的同义词,这是 (IMO) 不正确的。你的面试官可能是这么想的。如果是这种情况,那么当您提到“封装”时,你们每个人都在谈论两个不同的事情。


值得注意的是,这些概念在不同的编程语言中以不同的方式表示。几个例子:

  • 在 Java 和 C# 中,接口(以及在某种程度上,抽象类)提供抽象,而访问修饰符提供封装。
  • 这在 C++ 中几乎是一样的,只是我们没有接口,我们只有抽象类。
  • 在 JavaScript 中,duck 类型提供抽象,闭包提供封装。 (命名约定也可以提供封装,但这只有在各方同意遵循它的情况下才有效。)

【讨论】:

  • 你的意思是说“抽象是使用接口和抽象类实现的,而封装是使用私有和受保护的访问修饰符实现的。”?
  • @vishuminhas 这是将概念应用于 C# 时的最终结果。这些概念并不特定于 C#,并且可以在其他语言中具有其他表示。
  • 您能否告诉我说抽象最好从客户端代码的角度理解,而封装最好从服务代码(即封装类本身)的角度理解是否正确?跨度>
  • “抽象与分离接口和实现有关。” .那么并不总是对的?我们有一个臭名昭著的“抽象类”,它也可能提供一些实现。
  • 以下是对我的访问控制,而不是封装:"(...) 不允许访问或了解实现的内部结构"。它是一种实现封装的手段,即在隐藏实现细节的同时提供有用的操作。
【解决方案2】:

很简单!

以电视为例——它是封装,因为:

  1. 电视加载了我不知道的不同功能,因为它们完全隐藏。

  2. 音乐、视频等隐藏的东西都捆绑在一个我们称之为电视的胶囊中

现在,抽象是当我们对某事有所了解时,它可以帮助我们操纵我们不知道其内部工作原理的事物。

例如: 电视遥控器是抽象的,因为

  1. 使用遥控器,我们知道按数字键会改变频道。我们不知道内部实际发生了什么。我们可以操纵隐藏的东西,但我们不知道它是如何在内部完成的。

以编程方式,当我们能够以某种方式访问​​隐藏数据并知道一些东西时......就是抽象......当我们对内部一无所知时,它的封装。

没有遥控器,我们无法更改电视上的任何内容,我们必须查看它显示的内容,因为所有控件都被隐藏了。

【讨论】:

    【解决方案3】:

    抽象

    公开实体而不是实体的详细信息。

    “那里有细节,但我们不考虑它们。它们不是必需的。”

    示例 1:

    各种计算: 加法、乘法、减法、除法、平方、Sin、Cos、Tan。

    我们没有详细说明如何计算 Sin、Cos 或 Tan。我们只是显示计算器,它是各种方法,哪些是用户需要使用的。

    示例 2:

    员工拥有: 名字,姓氏,中间名。他可以 Login()、Logout()、DoWork()。

    登录员工可能会发生许多过程,例如连接到数据库,发送员工ID和密码,接收来自数据库的回复。虽然上面有详细信息,但我们将隐藏详细信息,只公开“员工”。

    封装

    封闭。将多个特征/功能视为一个单元而不是个体。 这样外部世界将直接引用该单元而不是它的详细信息。

    “那里有细节,我们会考虑它们,但不展示它们,而是展示你需要看到的东西。”

    示例 1:

    我们不再称它为加减乘除,而是称它为计算器。

    示例 2:

    所有特征和操作现在都由员工引用,例如“John”。约翰有名字。约翰可以做工作()。约翰可以 Login()。

    隐藏

    对外界隐藏实施。 让外面的世界看不到不该看的。

    “细节在那里,我们会考虑它们,但我们不会显示它们。你不需要看到它们。”

    示例 1:

    您的要求:加法、减法、乘法、除法。您将能够看到它并获得结果。

    您不需要知道操作数的存储位置。这不是你的要求。

    另外,我执行的每条指令,也不是你的要求。

    示例 2:

    约翰想知道他的出席率。所以 GetAttendancePercentage() 会被调用。

    但是,此方法需要将数据保存在数据库中。因此它将调用 FetchDataFromDB()。 FetchDataFromDB() 不需要对外界可见。

    因此我们将其隐藏。但是,John.GetAttendancePercentage() 将对外界可见。

    抽象、封装和隐藏相辅相成。

    因为我们在细节上创建了抽象级别,所以细节被封装了。因为它们是封闭的,所以它们是隐藏的。

    【讨论】:

      【解决方案4】:

      抽象和封装的区别:-

      抽象

      1. 抽象解决了设计层面的问题。
      2. 抽象用于隐藏不需要的数据并提供相关数据。
      3. 抽象让您专注于对象的作用,而不是它的作用。
      4. 抽象 - 外部布局,用于设计。 例如:- 手机的外观,就像它有一个显示屏和用于拨号的键盘按钮。

      封装

      1. 封装解决了实现层面的问题。
      2. 封装意味着将代码和数据隐藏在一个单元中,以保护数据不受外界影响。
      3. 封装意味着隐藏对象如何做某事的内部细节或机制。
      4. 封装 - 内部布局,用于实现。 例如:- 手机的内部实现细节,键盘按钮和显示屏如何使用电路相互连接。

      【讨论】:

        【解决方案5】:

        封装

        根据您在谷歌上搜索的知识,封装是一种将相关数据和操作组合在一个胶囊中的概念,或者我们可以说是 OOP 中的一个类,这样任何其他程序都无法修改它所保存的数据或方法实现它有,在特定的时间实例。只有 getter 和 setter 方法可以提供对实例变量的访问。

        我们的代码可能会被其他人使用,并且我们对未来的升级或错误修复负责。封装可以确保我们在代码中所做的任何代码更改都不会破坏使用它的其他人的代码。

        封装增加了代码的可维护性、灵活性和可扩展性。

        封装有助于将实现隐藏在接口后面。

        抽象

        抽象是将实现隐藏在接口后面的过程。所以我们只知道实际的行为,而不知道内部的想法是如何运作的。最常见的例子是将钥匙放入锁中并轻松解锁的场景。所以这里的界面是钥匙孔,而我们不知道锁内的杠杆如何相互协调以解锁锁。

        为了更清楚,抽象可以解释为对不同对象使用相同接口的能力。同一个接口可以存在不同的实现,而每个实现的细节都被封装隐藏了。

        最后,回答所有困惑的声明 - 隐藏的部分与封装有关,而暴露的部分与抽象有关。

        Read more on this here

        【讨论】:

          【解决方案6】:

          抽象:抽象是您收集或收集相关数据并删除不相关数据的过程。 (而且如果你实现了抽象,那么封装也实现了。)

          封装:封装是将函数和成员封装在一个单元中的过程。意味着您正在隐藏实现细节。意味着用户可以通过制作类的对象来访问,他/她看不到细节。

          示例:

           public class Test
             {
              int t;
              string  s;
           public void show()
            {
             s = "Testing";
             Console.WriteLine(s);
             Console.WriteLine(See()); // No error
            }
          
           int See()
            {
             t = 10;
             return t;
            }
          
           public static void Main()
            {
            Test obj =  new Test();
            obj.Show(); // there is no error
            obj.See(); // Error:- Inaccessible due to its protection level
            }
           }
          

          在上面的例子中,User 只能使用 obj 访问 Show() 方法,也就是 Abstraction。

          并且 See() 方法是在 Show() 方法内部调用,这是封装,因为用户不知道 Show() 方法中发生了什么。

          【讨论】:

            【解决方案7】:

            我知道有很多答案摆在我面前,有各种各样的例子。
            那么这是我的观点抽象正在从现实中获得兴趣

            抽象中,我们隐藏了一些东西以降低复杂性 而在封装中,我们隐藏了一些东西来保护数据。

            因此,我们将封装定义为将数据和方法包装在称为类的单个实体中。

            在 java 中,我们使用 getter 和 setter 来实现封装,而不仅仅是通过在其中包装数据和方法。我们还定义了一种访问该数据的方法。 在访问数据时,我们也会保护它。

            技术上的例子是定义一个私有数据变量调用权重。现在我们知道,在现实世界的场景中权重不能为零或小于零。
            想象一下,如果没有 getter 和 setter 的人作为类的公共成员,本可以轻松地将其设置为负值。

            现在使用一个真实世界的示例进行最终区别,
            考虑一个由开关和按钮组成的电路板。 我们将所有电线包裹在一个电路盒中,这样我们就可以通过不直接接触来保护某人(封装)。

            我们不在乎这些电线是如何相互连接的,我们只需要一个接口来打开和关闭开关。该界面由按钮提供(abstraction

            【讨论】:

              【解决方案8】:

              封装:假设我有一些机密文件,现在我将这些文件隐藏在一个储物柜中,这样任何人都无法访问它们,这就是封装。

              摘要:发生了一件大事,在报纸上做了总结。现在报纸只列出了实际事件中比较重要的细节,这是抽象的。此外,事件的标题在一行中突出显示了更具体的细节,从而为事件提供了更高级别的抽象。足球/板球比赛的精彩片段也可以被视为整个比赛的抽象。

              因此,封装隐藏了数据以保护其完整性,而抽象则突出了更重要的细节。

              在编程术语中我们可以看到,一个变量可能被封闭是一个类的范围是私有的,因此防止它被直接从外部访问,这是封装 .而一个函数可以写在一个类中来交换两个数字。现在可以通过使用临时变量或通过位操作或使用算术运算来交换数字,但用户的目标是接收交换的数字,而与用于交换的方法无关,这是抽象。

              【讨论】:

                【解决方案9】:

                抽象:在硬件抽象层的情况下,您有简单的接口来触发硬件(例如向左/向右转动发动机),而无需了解背后的硬件细节。所以隐藏了系统的复杂性。这是对现实世界的简化视图。

                封装:隐藏对象内部。对象是现实世界的抽象。但是这个对象的细节(比如数据结构......)可以通过封装隐藏。

                【讨论】:

                  【解决方案10】:

                  抽象是指表示基本特征而不包括背景细节或解释的行为。

                  封装是一种用于隐藏对象的属性和行为并仅在适当时允许外部访问的技术。它可以防止其他对象直接更改或访问被封装对象的属性或方法。

                  抽象与封装的区别

                  1.抽象关注对象的外部视图(即接口)封装(信息隐藏)防止客户看到它的内部视图,抽象的行为在其中实现。

                  2.抽象解决了设计方面的问题,而封装是实现。

                  3.封装是抽象的交付物。封装几乎不涉及对抽象进行分组以满足开发人员的需求。

                  【讨论】:

                    【解决方案11】:

                    抽象:“提取基本信息的问题视图 与特定目的相关而忽略其余部分 信息。”[IEEE,1983]

                    ENCAPSULATION:“封装或等效的信息隐藏是指 在一个对象中包含它所需要的一切的做法,以及 此外,这样做的方式是任何其他对象都不需要 注意这个内部结构。”

                    【讨论】:

                      【解决方案12】:

                      抽象数据封装的众多好处之一。我们也可以说数据封装是实现抽象的一种方式。

                      【讨论】:

                        【解决方案13】:

                        我对抽象的意见并不是隐藏实现或背景细节!

                        抽象使我们能够处理更容易处理的现实世界的表示,具有被重用的能力,可以与我们或多或少复杂的程序包的其他组件相结合。所以我们要找出how我们选择一个完全平静的现实世界,它足够完整地代表我们的算法和数据的感觉。接口的实现可能会隐藏细节,但这不是我们必须做的抽象工作的一部分。

                        对我来说,抽象最重要的是:

                        1. 降低复杂性
                        2. 减少尺寸/数量
                        3. 将不相关的域拆分为清晰的独立组件

                        这一切对我来说与隐藏背景细节无关!

                        如果您考虑对某些数据进行排序,抽象可能会导致:

                        1. 一种独立于数据表示的排序算法
                        2. 独立于数据和排序算法的比较函数
                        3. 一种通用数据表示,独立于所使用的算法

                        所有这些都与隐藏信息无关。

                        【讨论】:

                          【解决方案14】:

                          在我看来,封装是程序员通过使用访问说明符来隐藏程序代码的复杂性的一种想法。
                          抽象是根据那里的功能和行为分离方法和对象。例如,汽车有床单、车轮、刹车、前灯。

                          【讨论】:

                            【解决方案15】:

                            本质上利用抽象概念的开发人员A将使用模块/库函数/小部件,只关心做什么(以及做什么) 它将用于)但不是 如何 它是这样做的。该模块/库函数/小部件的接口(允许开发人员 A 拉/推的“杠杆”)是该抽象的拟人化。

                            正在寻求创建这样一个模块/功能/小部件的开发人员 B 将利用 封装 的概念来确保开发人员 A(以及使用该小部件的任何其他开发人员)可以利用产生的抽象。开发人员 B 最关心的是 小部件如何完成它的工作。

                            TLDR;

                            • 抽象 - 我关心 什么 某事做了什么,而不是 如何 它做到了。
                            • 封装 - 我关心 如何 某物做什么,因此其他人只需要关心 它做什么

                            (作为一个松散的概括,要抽象一些东西,你必须封装一些别的东西。通过封装一些东西,你就创建了一个抽象。)

                            【讨论】:

                              【解决方案16】:

                              抽象的本质是客户端代码按照不同逻辑/抽象模型运行。这种不同的模型可能比任何给定客户端使用中的实现更或更少复杂。

                              例如,“迭代器”抽象(又名泛化)0 个或多个值的顺序遍历 - 在 C++ 中,它表现为begin()*/->(取消引用)、end()、前/后@ 987654326@ 和可能的 --,然后是 ++=[]std::advance 等。如果客户可以说无论如何沿数组递增 size_t,那就太麻烦了。重要的是,抽象允许需要执行这种遍历的客户端代码与提供元素的“容器”或数据源的确切性质分离。迭代是一个更高级别的概念,它有时会限制执行遍历的方式(例如,前向迭代器一次只能推进一个元素),但数据可以由更大的源集提供(例如,从键盘在并发存储值的意义上,甚至没有“容器”)。客户端代码通常可以切换到另一个通过其自己的迭代器抽象出来的数据源,只需很少甚至没有更改,甚至可以多态地切换到其他数据类型——无论是隐式还是显式地使用 std::iterator_traits<Iterator>::value_type 等可用的东西。

                              这与封装完全不同,封装是使某些数据或函数的可访问性降低的做法,这样您就知道它们只是作为公共接口上的操作的结果而被间接使用。封装是在对象上维护不变量的重要工具,这意味着您希望在每次公共操作之后保持真实的事情 - 如果客户端代码可以直接进入并修改您的对象,那么您无法强制执行任何操作不变量。例如,一个类可能会包装一个字符串,确保在任何操作之后将任何小写字母都更改为大写,但是如果客户端代码可以在不涉及类的成员函数的情况下将小写字母放入字符串中,那么不能强制执行不变量。

                              为了进一步突出差异,请考虑说一个 private std::vector<Timing_Sample> 数据成员,该数据成员偶然由对包含对象的操作填充,并在销毁时转储报告。由于数据和析构函数的副作用不会以任何方式与对象的客户端代码交互,并且对象上的操作不会有意控制计时行为,因此没有时间报告功能的抽象,但存在封装。抽象的一个例子是将计时代码移动到一个单独的类中,该类可能封装vector(使其成为private)并仅提供像add(const Timing_Sample&)report(std::ostream&)这样的接口——所涉及的必要逻辑/抽象操作使用此类工具,具有非常理想的副作用,即抽象代码通常可重用于具有类似功能需求的其他客户端代码。

                              【讨论】:

                                【解决方案17】:

                                在我看来,这两个术语在某种意义上是相关的,并且是相互混合的。 “封装”提供了一种将相关字段、类(或模块)中的方法分组以将相关事物包装在一起的方法。截至当时,它以两种方式提供数据隐藏;

                                1. 通过访问修饰符。

                                  纯粹是为了隐藏类/对象的状态。

                                2. 抽象一些功能。

                                  一个。通过接口/抽象类,可以将封装的类或模块内部的复杂逻辑抽象/泛化以供外部使用。

                                  b.通过函数签名。是的,甚至是抽象的函数签名示例。因为调用者只知道签名和参数(如果有的话),而对函数的执行方式一无所知。它只关心返回值。

                                同样,“抽象”可能被认为是一种封装方式,将行为分组/包装到接口(或抽象类,甚至可能是普通类)中。

                                【讨论】:

                                  【解决方案18】:

                                  就iOS而言,可以说Objective C文件(即.h和.m)使用抽象和封装。

                                  抽象

                                  头文件(.h)只向外界公开函数和公共成员。除非他们有实现文件,否则没有人知道它们是如何使用的。它是 .m 文件,其中包含所有使用和实现逻辑。 “实施仍未公开”。

                                  封装

                                  属性 (@property) 封装了 iVar 的内存管理属性(atomic、strong、retain、weak)。

                                  【讨论】:

                                    【解决方案19】:

                                    一个程序主要有两部分:DATA和PROCESS。抽象隐藏了过程中的数据,因此没有人可以更改。封装将数据隐藏在任何地方,使其无法显示。 我希望这能澄清你的疑问。

                                    【讨论】:

                                      【解决方案20】:

                                      使用封装有两个主要原因:

                                      1.) 数据隐藏和保护(您班级的用户只能通过您提供的方法修改数据)。

                                      2.) 将数据和用于操作数据的方法组合到一个实体(胶囊)中。 我认为第二个原因是你的面试官想听到的答案。

                                      另一方面,需要抽象来只向用户公开需要的信息,并隐藏不需要的细节(例如,隐藏方法的实现,这样如果出现以下情况,用户就不会受到影响)实现已更改)。

                                      【讨论】:

                                        【解决方案21】:

                                        抽象:隐藏数据。 封装:绑定数据。

                                        【讨论】:

                                          【解决方案22】:

                                          为什么要封装?为什么要抽象?

                                          让我们从以下问题开始:

                                          1)如果我们允许代码直接访问字段会发生什么? (直接允许意味着使字段public

                                          让我们通过一个例子来理解这一点,

                                          following is our BankAccount class and following is its limitation
                                          *Limitation/Policy* : Balance in BankAccount can not be more than 50000Rs. (This line 
                                           is very important to understand)
                                          
                                          class BankAccount
                                          {
                                             **public** double balanceAmount;
                                          }
                                          
                                          Following is **AccountHolder**(user of BankAccount) class which is consumer of 
                                          **BankAccount** class.
                                          
                                          class AccountHolder
                                          {
                                             BankAccount mybankAccount = new BankAccount();
                                          
                                             DoAmountCreditInBankAccount()
                                             {
                                                 mybankAccount.balanceAmount = 70000; 
                                                /* 
                                                 this is invalid practice because this statement violates policy....Here 
                                                 BankAccount class is not able to protect its field from direct access
                                                 Reason for direct access by acount holder is that balanceAmount directly 
                                                 accessible due to its public access modifier. How to solve this issue and 
                                                 successfully implement BankAccount Policy/Limitation. 
                                                */
                                             }
                                          }
                                          

                                          如果代码的其他部分直接访问 balanceAmount 字段并将余额金额设置为 70000Rs,这是不可接受的。在这种情况下,我们不能阻止代码的其他部分访问 balanceAmount 字段。

                                          那么我们能做些什么呢?

                                          => 答案是我们可以将 balanceAmount 字段设为私有,这样其他代码就不能直接访问它,并且只允许通过对 balanceAmount 字段进行操作的公共方法访问该字段。方法的主要作用是我们可以在方法内部编写一些预防逻辑,使字段不能超过 50000Rs 初始化。在这里,我们在称为 balanceAmount 的数据字段和在该字段上操作的方法之间进行绑定。这个过程称为封装。(这都是关于使用访问修饰符(如私有)保护字段)

                                          封装是实现抽象的一种方式....但是如何? =>此方法的用户将不知道他/她将调用的方法的实现(如何记入贷方?逻辑和所有这些东西)。不知道用户的实现细节称为抽象(向用户隐藏细节)。

                                          Following will be the implementation of class:
                                          
                                          class BankAccount
                                          {
                                             **private** double balanceAmount;
                                          
                                             **public** void UpdateBankBalance(double amount)
                                             {
                                              if(balanceAmount + amount > 50000)
                                              {
                                                 Console.WriteLine("Bank balance can not be more than 50000, Transaction can 
                                                                    not be proceed");
                                              }
                                              else
                                              {
                                                 balanceAmount = balanceAmount + amount;
                                                     Console.WriteLine("Amount has been credited to your bank account 
                                                                        successfully.....");
                                              }
                                             }
                                          }
                                          
                                          
                                          class AccountHolder
                                          {
                                             BankAccount mybankAccount = new BankAccount();
                                          
                                             DoAmountCreditInBankAccount()
                                             {
                                                mybankAccount.UpdateBankBalance(some_amount);
                                                /*
                                                 mybankAccount.balanceAmount will not be accessible due to its protection level 
                                                 directly from AccountHolder so account holder will consume BankAccount public 
                                                 method UpdateBankBalance(double amount) to update his/her balance.
                                                */
                                             }
                                          }
                                          

                                          【讨论】:

                                            【解决方案23】:

                                            封装基本上是拒绝外部世界访问内部实现或有关内部的知识,而抽象是对任何有助于外部世界的实现的通用视图与它互动的世界

                                            【讨论】:

                                              【解决方案24】:

                                              简单地说,抽象就是使与对象交互的必要信息可见,而封装使开发人员能够实现所需的抽象级别。

                                              【讨论】:

                                                猜你喜欢
                                                • 1970-01-01
                                                • 2013-02-17
                                                • 1970-01-01
                                                • 2015-09-26
                                                • 2018-11-04
                                                • 2010-10-10
                                                • 2013-10-17
                                                • 2010-09-26
                                                • 2011-01-18
                                                相关资源
                                                最近更新 更多