抽象和多态有什么区别?

44

我似乎不太理解两个面向对象编程(OOP)的概念。能否用真实的例子和代码来解释一下抽象(abstraction)和多态(polymorphism)是什么意思呢?

谢谢。


5
我认为这听起来不像作业,我会说他/她只是在理解上遇到了困难。 - Nathan W
1
这不是作业,只是当我和我的团队讨论时,这些概念变得有点相似。这就是为什么它们让我感到困惑。抽象指的是某物的没有具体细节,而多态性则指不同对象的方法具有相同但执行不同任务。我说得对吗? - Sambath Prum
11个回答

29

抽象

想象一个分数类:

class fraction:
    int denominator
    int numerator

现在有两个对象:

fraction(obj1): denominator=-1 numerator=-1
fraction(obj2): denominator=1  numerator=1

两个对象的值都为1:(1/1) == (-1)/(-1)。你不会期望它们在外部表现出任何不同。这就是抽象化。你将对象持有的数据抽象成一个逻辑视图,尽管在幕后还有其他东西。从理论上讲,你拥有了一个等价关系,具有不同的等价组:

[1]=(1, 1), (-1, -1), (5, 5), ...
[2]=(2, 4), (-2, -4), ...
...

有一个抽象函数,它将内部细节抽象出来,呈现给外部:

f((1, 1)) = [1]
f((-1, -1)) = [1]

它将从具体值映射到对象的抽象值。您可以通过编写构造函数映射(-1,-1)到(1,1),并为类编写equals函数来实现这一点。

多态性

想象一支笔和两个派生类:

class pen:
    void draw(int x, int y)

class pen_thin extends pen:
    void draw(int x, int y) { color(x, y) = green; }

class pen_thick extends pen:
    void draw(int x, int y) { color(x, y) = green; 
                              color(x, y+1) = green; }
and two objects:
    pen_thin(p1)
    pen_thick(p2)

两个笔都可以画画。普通的“笔”本身无法画画,它只是接口,用来调用细笔、粗笔和许多其他笔。你可以这样说:obj1.draw(1,0);不管obj1是粗笔还是细笔,对于用户和编译器在编译时都没有影响。这个调用是多态的。这是动态多态性(发生在运行时),这通常是人们所说的。 静态多态性发生在编译时:

class colorizer:
    void colorize(shirt s)
    void colorize(pants p)

这称为重载。您调用obj.colorize(something)。如果您使用衬衫引用调用它,它将调用采用衬衫的版本。如果您使用裤子引用调用它,则会调用裤子版本。此处所做的选择是在编译时进行的。


19

抽象和多态是关键概念,绝不仅限于面向对象编程。更加混乱的是,“抽象”这个词有多种用法。以下是一个快速的备忘单,并附有一个示例:

  • 数据抽象指的是信息隐藏。通常隐藏的是数据结构的表示方式。例如:我实现了集合,但我不告诉您集合是作为列表、平衡二叉树还是非平衡二叉树表示的。如果做得好,我可以更改表示而不破坏您的代码

  • 多态意味着使用不同类型进行重用。因此,在我的集合示例中,您可以创建社会保障号码的集合、全名的集合或果蝠的集合,所有这些都使用相同的代码。

显然,您可以定义一个既是抽象的又是多态的类。

多态进一步令人困惑,因为有两种实现多态的方法。在参数化多态中,您可以重用具有任何类型值的集合,或者可能满足某些约束条件的任何类型。最明显的例子是C++模板;如果您编写

class Set <T> { ... }

然后T是集合中包含的对象的类型(符号<T>表示所谓的“类型参数”,这使它成为参数化多态性)。
子类型多态性中,您只能重复使用类型为特定类型的子类型的对象的集合。例如,您可能只能制作提供小于或等于方法的对象的集合。在真正的面向对象语言(如Smalltalk或Ruby)中,它们提供所谓的鸭子类型(有时我们这些聪明的理论家称之为行为子类型),仅存在该方法即可。在像Java或C++这样的语言中,它们将子类型和继承混淆,您对多态性的使用可能会受限于特定类的子类。(Java通过在类和接口上使用一种形式的子类型来进一步混淆问题。)
最后,像我这样的老人谈论过程抽象,这意味着能够将经常一起使用的一堆语句放入可以重用的过程或方法中。这可能与您的问题无关。
所以,您是否对被困惑感到更好了?

那么,数据抽象和特设多态有什么区别呢? - Aadit M Shah

16

这两个特性是面向对象范式中最重要的特征之一。

抽象。

面向对象将软件建模为现实世界的对象。然而,模拟一个客户所可能拥有的所有属性,或者所有雇员所可能拥有的属性将会非常困难(并且没有用处)。

通过仅列出一个对象的有趣属性,面向对象可以有效地在特定领域使用该对象。这就是抽象。

例如,在HR系统中,员工可能具有与在线书店非常不同的属性。我们对细节进行抽象处理以使其有用。

多态性。

对象可以根据“类型”而表现出不同的行为,同时保持相同的接口。

这意味着什么?

例如,一个在线商店系统可能有两个子类的员工:

A)内部员工。

B)承包商。

还有一种计算内部购买折扣的方法:

内部员工的折扣计算为:公司工作年限的每2年增加2%+每个孩子增加2%。

承包商的折扣为10%。

以下是计算支付金额的代码:

 public Amount getAmountToPay( Product product, Employee internalCustomer ) { 
      Amount amount = product.getPrice();
      amount.applyDiscount( internalCustomer.getDiscount() );
      return amount;
 }

两种不同类型的员工会产生不同的结果。

class Employee { 
    public int getDiscount();
}


class InternalEmployee extends Employee { 
     public int getDiscount() { 
        return 10 + 2 * getWorkedYears() + 2 * getNumberOfChilds();
     }
 }

 class Contractor extends Employee { 
      public int getDiscount() { 
          return 10;
     }
 }

这就是多态的体现。与其像这样拥有

 Amount amount = product.getPrice();

 if( employee.isContractor() ) { 
      amount.applyDiscount( 10 );
 } else if( employee.isSomthingElse() ) {
      amount.applyDiscount( 10 * 2 * getYrs() + 2 * getChilds() );
 } else if ( employee.contidions, condigions, conditions ) {
      amount.applyDiscount( getSomeStrageRuleHere() );
 }

我们让运行时自行选择要计算哪个。就像程序根据类型的不同而表现出不同的行为:

      Amount amount = product.getPrice();
      amount.applyDiscount( internalCustomer.getDiscount() );
      return amount;

顺便提一下,在这个例子中,“Amount”是一个真实概念的抽象,它也可以表示为双精度浮点数或整数,但也许我们有一些有趣的方法需要放在自己的类中更好地设置。

希望这有所帮助。


优秀的解释! - Student

9

抽象是指在不包含背景细节或解释的情况下表示必要特征的行为。类使用抽象概念,并被定义为一组抽象属性。

软件抽象的一个例子是Java的Object.equals(Object o)方法。您知道它将比较此对象与传递的参数对象,但您不知道也不需要知道如何实现它(除非您是该类的实现者)。

多态意味着具有多种形式的能力。在不同的实例中,方法可能具有不同的行为。行为取决于操作中使用的数据类型。

多态的经典示例之一使用以Animal类为根的继承树。所有Animal都有一个makeNoise()方法,但Dog类和Cat类以不同的方式实现它。这使您可以使用Animal引用类型引用任何Dog和Cat。

Animal a = new Dog();
Animal b = new Cat();

现在您可以在任何动物实例上调用makeNoise()方法,并知道它会发出适当的噪音。如果您有一组动物,并且在运行时不确定每个动物实际上是什么类型,这将特别有用。

5
简短回答:抽象是概念上的,多态是行为上的。

4

这两个术语在面向对象编程中被广泛使用,但它们并不特定限于该上下文。

抽象是对其他事物的概括; 更高层次的一步。例如,层次结构可以被视为公司组织结构的抽象。通常它用于底层的东西(如它们的基础类型)的上下文中。抽象的重点在于编写更少的代码,更具有一般性,以便您可以针对更大的问题集运行它。例如,电子表格是一种允许特定类型信息存储的抽象。更多?

多态性也是一种泛化,但它发生在运行时上下文中。如果有一种方式可以访问不同的对象类型并且它们彼此无法区分,则这些对象类型是多态的。也就是说,所有对象看起来和感觉都是相同的,即使它们并不相同。其目的是显着减少代码量;您可以编写一个通用解决方案,以节省编写每个不同类型的不同排列的代码。如果您编写了一个图形库,您宁愿只编写一些处理“形状”的抽象代码,而不必为每种不同类型(如圆形、正方形等)编写代码。

这两个术语都是围绕代码中的属性进行的,这些属性将使程序员能够用更少的代码实现更多的功能。较少的代码具有较少的错误,更稳定,并且更易于维护。另一种选择是使用“蛮力”来打出数百万行非常特定(而且非常脆弱)的代码。更多的代码更难修复,并且更难保持最新。

保罗。


3
在面向对象编程中,抽象的实际含义让人感到困惑是可以理解的:它对继承、封装甚至多态的概念添加了很少或者没有什么东西。如果你掌握了这三个概念,那么就没有必要过于关注“抽象”,因为它自然地融入了它们之中(特别是继承)。
首先,请注意,“抽象”一词具有多种含义,并且说“封装需要抽象”并不是不正确的:当您使用访问修饰符来保护类的属性同时公开处理它们的方法时(这就是封装),类的用户不再需要担心如何亲自处理它们。因此,在某种意义上,当您设计一个类时,通过适当地封装方法和属性,您就进行了抽象——类的用户所需做的就是调用正确的方法来使用它,这是一种抽象形式。
此外,如果您思考得清楚,多态也是一种抽象形式:您的代码调用由某个类提供的方法,直到确定实际类类型(在运行时)之前,您不知道它会起什么作用。因此,正确地说多态行为是一种抽象形式。
然而,当作为一个独立的术语用于描述面向对象编程的特征时,抽象必须被理解为以合适的类层次结构的形式来适当地表示所讨论的系统。因此,抽象是设计者思维过程的结果,最终得出一个适用于程序中要使用的类的合适设计。引用一篇(非常好的!)文章可以在javarevisited博客上找到:

... 抽象在设计层面上隐藏了细节,而封装在实现层面上隐藏了细节。

虽然上述说法是正确的,但我认为“隐藏细节”的部分表述不准确 - 我会重新用这样的话来表达:

抽象涉及设计细节,决定类层次应该是什么样子的,封装则隐藏实现细节

为了公正起见,作者在文章中非常漂亮地表达了这个想法。像《Head First面向对象分析与设计》这样的好书也使用了这个意义上的“抽象”一词。我引用其中的一句话:“无论何时你在两个或更多的地方发现共同的行为,请尝试将该行为抽象成一个类,然后在常见的类中重复使用该行为。”请注意这里抽象的用法:““look to abstract that behavior into a class”。如果to abstract的意思是按照上面建议的正确地设计类层次结构,那么抽象可以被定义为利用继承和封装的概念方便地使用类来表示域
在Java的特定情况下,抽象是通过使用接口抽象类来实现的,而封装是通过使用私有、受保护和包访问修饰符来实现的。

3
简单来说,抽象是概念上的,而多态是行为上的。在面向对象编程中实现抽象,需要使用多态。
面向对象编程中的抽象是一种概念或设计模式,它能够实现更好的隔离、松耦合、可测试性以及重用和可扩展性。为了实现这些,我们需要使用多态、继承/扩展等技术。

或者,作为替代方案,设计提供了多态性,以便抽象化使其成为可能。 - lifebalance

2

抽象和多态在本质上相似但目的不同。

例如:

驾照:颁发给你的驾照会注明你可以驾驶的车辆类别。驾照注明了所允许的车辆类型,但没有定义或注明你应该驾驶哪种具体汽车或品牌。这就是抽象。

在这里,驾照是一个抽象类,其方法允许车辆是它的抽象方法

现在,多态是指机构授权给不同人使用不同方式的驾照。一些驾照适用于轻型车辆,而另一些适用于重型车辆,还有一些适用于商用车辆,根据不同的需求进行发放。在这里,驾照是一个基类,而其他种类的驾照是它的子类,也遵循is-a关系。商用驾照是一种驾照。

因此,抽象是给跟随者类提供实现独立性的一般准则,而多态是覆盖父类设置的方法/规则的不同方法。


0

附言:最近开始学习Java,我的答案基于我的观察,请纠正我如果我错了。

在编程中,抽象和多态基本上在深层次上做着相同的工作。

以汽车为例。

无论是福特小型货车、法拉利跑车、路虎SUV还是宝马轿车,它们都遵循汽车的基本设计,即发动机、方向盘、变速箱、灯光、指示器等等。它们之间的区别在于它们的具体实现,例如法拉利可能比小型货车拥有更强大的发动机,SUV可能有不同的变速箱,因此汽车(超类)已经被子类(轿车、SUV、小型货车、跑车)所实现。这就是多态,一个基本的想法通过添加其他规范来继承或实现。一个四轮车辆(超类)以各种形式(子类)被实现。

现在,抽象,根据定义,它意味着隐藏细节并使用户看到他所需的内容。

再以汽车为例。你使用变速器,但你不知道变速器的机制如何工作和改变速度等。

现在转到编码部分。

抽象类是不完整的类,如其名称所示,它们需要有一个未完成的方法,需要被继承超类的子类完成。如果他们不完成抽象方法,它们也将保持不完整的状态。

abstract class car {
  abstract void gear();
}

class sedan extends car {
 public void gear()
 {
  //complete the method
 }
}

由于类不完整,因此您无法创建抽象类的对象。然而,这些抽象类可以具有静态方法、参数、具体方法,但为了使它们成为抽象类,它们需要一个抽象方法。因此,在其他子类中实现了一个基本的抽象超类,其中它们完成了它。

通过查看方法声明,我们可以估计方法实际上在做什么,它将返回什么。但是我们不知道抽象方法将如何实现。

通过使用抽象类或接口,我们可以在Java中实现抽象化。正如我们所知道的那样,抽象类和接口包含抽象方法。

我们只能估计它们将如何工作。一旦我们在实现相应的抽象类或接口的类中提供了方法实现,我们就会知道它们的工作方式。

因此,抽象基本上有助于多态性。


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