Java中的抽象与封装

91
尽管互联网上充满了这些概念的各种定义,但对我来说它们仍然听起来都一样。例如,请考虑以下定义:封装是将操作数据的代码和数据绑定或包装到单个实体中的过程。这可以使数据免受外部接口和误用的威胁。可以将封装视为一种保护性包装,防止其他定义在包装外部的代码随意访问该代码和数据。

从上面的定义中我理解的是创建变量,将其标记为私有,并为这些变量生成getter-setter,并使用对象来访问这些getter和setter。通过这种方式,数据被隐藏在对象内部,并且只能通过对象访问。

这正确吗?


抽象是Java中用于隐藏某些细节并仅显示对象的基本特征的过程。换句话说,它处理对象的外部视图(接口)。

现在这部分总是让我困惑。每当我想到抽象时,我脑海中浮现出的是抽象类(可能是因为两者都具有抽象关键字)。上面的定义说抽象意味着隐藏数据并仅显示所需的详细信息,但这不是我们在封装中已经在做的吗?那么区别在哪里?我也不明白“它处理对象的外部视图”这句话的意思。

有人能更清楚地解释一下吗?实际生活和/或编程示例将特别有帮助。

3个回答

93

面向对象抽象发生在类级别设计过程中,其目的是为了隐藏API / 设计 / 系统提供的功能是如何被实现的,从而简化访问底层实现的“接口”,简化系统的复杂度。

抽象的过程可以在越来越高层次(层)的类中重复,这使得可以构建大型系统,而不会增加每个层次的代码复杂性和理解难度。

例如,Java开发人员可以使用FileInputStream的高级特性,而不必考虑其工作原理(即文件句柄,文件系统安全检查,内存分配和缓冲区管理将在内部处理并对用户隐藏)。这允许更改FileInputStream的实现,只要与FileInputStream交互的API(接口)保持一致,之前版本的代码仍然可以工作。

同样,在设计自己的类时,您将希望尽可能地隐藏内部实现细节,以便他人无法修改。

在Booch的定义中1面向对象封装通过信息隐藏实现,特别是在控制访问内部数据(表示状态的字段/成员)的同时,防止直接、外部地更改这些字段,并且隐藏类的任何内部实现方法(例如,通过将它们设置为私有)。

例如,类的字段可以默认设为private,只有在需要外部访问这些字段时,才会从类中公开get()和/或set()(或Property)。 (在现代面向对象语言中,字段可以标记为readonly / final / immutable,这进一步限制了对字段的更改,即使在类内部也是如此)。

没有应用信息隐藏的示例(不好的做法):

class Foo {
   // BAD - NOT Encapsulated - code external to the class can change this field directly
   // Class Foo has no control over the range of values which could be set.
   public int notEncapsulated;
}

应用字段封装的示例:

class Bar {
   // Improvement - access restricted only to this class
   private int encapsulatedPercentageField;

   // The state of Bar (and its fields) can now be changed in a controlled manner
   public void setEncapsulatedField(int percentageValue) {
      if (percentageValue >= 0 && percentageValue <= 100) {
          encapsulatedPercentageField = percentageValue;
      }
      // else throw ... out of range
   }
}

不可变/仅构造函数初始化字段的示例

class Baz {
   private final int immutableField;

   public void Baz(int onlyValue) {
      // ... As above, can also check that onlyValue is valid
      immutableField = onlyValue;
   }
   // Further change of `immutableField` outside of the constructor is NOT permitted, even within the same class 
}

关于抽象化与抽象类

抽象类是指促进类之间共性重用的类,但是抽象类本身不能直接使用new()实例化 - 抽象类必须被子类化,只有具体的(非抽象的)子类可以被实例化。一个导致人们混淆Abstractionabstract class的原因是,在面向对象编程的早期阶段,继承更多地被用来实现代码重用(例如通过相关联的抽象基类)。现在,组合通常优于继承,同时还有更多的工具可用于实现抽象化,例如通过接口、事件/委托函数、特征/混入等。

关于封装与信息隐藏

封装的含义似乎随着时间的推移而发生了变化,在最近,封装也可以常用于更一般的意义上,即确定将哪些方法、字段、属性、事件等打包到一个类中。

引用维基百科的话:

在面向对象编程语言的更具体的环境中, 这个概念被用来指代信息隐藏机制、打包机制,或者两者的结合.

例如,在语句

我已经将数据访问代码封装到它自己的类中了

.. 封装的解释大致相当于关注点分离或者单一职责原则(SOLID中的"S"),也可以说是重构的同义词。


[1] 如果您看过Booch的封装猫图片,您将永远无法忘记封装 - 来自《面向对象分析与设计的应用》第二版的46页


我们使用的Java API是一个抽象层吗? - sras
1
是的,在高层次上,API确实提供了一个更简单的抽象,供消费者使用,他们对其内部设计背后的复杂性毫不知情。Java显然是一种语言,具有许多相关的高级框架。然而,我认为更具体地说,在面向对象设计原则术语被创造出来的时候,抽象更具体地指的是类级别的设计和分解,而不是指框架或企业级别的设计。 - StuartLC

54

简单来说: 在决定要实现什么时,您会进行抽象。 在隐藏您已经实现的东西时,您会进行封装。


37

抽象化是关于识别共同点,并减少你在不同代码层次上所需处理的特征。

例如,我可能有一个Vehicle类。一个Car会派生自Vehicle,一个Motorbike也一样。我可以向每个Vehicle询问车轮数量、乘客数量等信息,并且这些信息已被抽象化和识别为来自CarsMotorbikes的共同点。

在我的代码中,我通常可以通过常用方法go()stop()等来处理Vehicles。当我稍后添加新的车辆类型(例如Scooter)时,我的大部分代码将忽略此事实,而只有Scooter的实现需要考虑Scooter的特定问题。


请问您能否提供代码以更好地解释您的答案? - Rahul Gupta
1
为了完全理解这个,你需要理解继承的概念。通过继承,您可以创建一个类的层次结构,父类(高级别父类是抽象的,大多数子类是具体的),并将共同的特征/属性传递给子类。如果正确实现,则实现了抽象化。
  • 父类执行共性。(如果用户专注于子类,则从用户角度隐藏)
  • 子类专注于成为一个孩子。(俗语:作为一个孩子,我不关心我的父母的工作,但我关心的是我所做的事情。)
- cattarantadoughan

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