什么是多态?它的作用是什么?它如何被使用?

637

什么是多态性,它有什么作用,以及它如何被使用?


20
@John: +1我同意这是一个非常有趣的现象。我相信,像Unkwntech一样有知识和能力的人也会在其他人认为是基本词汇方面存在空白。这说明编程是一个非常广泛的主题。 - AnthonyWJones
11
他可能会使用它,只是不给它命名。 - Aiden Bell
10
@Aamir:我不确定假设一个拥有8k声望的人会知道编程所有领域的基础是否合理。我也认为这并不意味着声誉系统是不完善的。一个人可以通过提出大量好问题来获得相当多的声誉。我认为我们对这一揭示的自然反应仅仅表明了我们(程序员)有一种天生的倾向,即有点狭隘(当我们需要在某些特定技术领域非常优秀时这并不是坏事),但这也有其不利之处。 - AnthonyWJones
45
你们似乎对编程有一个非常狭隘的看法。我认识一些从事嵌入式开发的人,他们根本不了解(或不需要)面向对象的概念。他们的任务是尽可能地从代码中获取最后一点性能,而且他们所开发的代码将永远不会进入对象世界。幸运的是,他们离退休年龄越来越近,无需担心学习像对象、多态和变量名超过两个字母之类的新概念 :-) - paxdiablo
37
你是如何学习一项技能的?没有人生来就会PHP面向对象编程和设计模式,因此你们所有人都必须在某个时候学习它,无论是在大学里还是通过这里的回答等。不要谈论某个人“不敢知道复杂的代码程序”,相反,考虑他们在这里想要学习它,这是一个好事情,也是这个网站存在的意义所在。用你的时间帮助他们,就像我相信过去别人也曾经帮助过你一样。如果在人类历史上,一直以来我们的反应都是“什么?哈!你不知道那个吗?...”,那么我们现在可能还处于黑暗时代。 - James
显示剩余12条评论
29个回答

602

如果你考虑一下这个术语的希腊词根,它应该变得很明显。

  • Poly = many: polygon = 多边形, polystyrene = 多苯乙烯(a), polyglot = 多语种,等等。
  • Morph = 变化或形态:morphology=生物形态学,Morpheus=希腊梦之神,能够呈现任何形态。

因此,多态是编程中的一种能力,可以对不同基础形式(数据类型)提供相同的接口。

例如,在许多语言中,整数和浮点数隐式地具有多态性,因为您可以进行加、减、乘等操作,而不管类型是否不同。它们很少被视为通常术语下的对象。

但是,以同样的方式,像BigDecimal、Rational或Imaginary这样的类也可以提供这些操作,即使它们操作的是不同的数据类型。

经典的例子是Shape类和所有可以从它继承的类(正方形、圆形、十二面体、不规则多边形、splat等)。

通过多态性,每个这些类将具有不同的基础数据。一个点形状只需要两个坐标(当然假设它在二维空间中)。一个圆需要一个中心和半径。正方形或矩形需要两个坐标,分别为左上角和右下角,以及(可能)一个旋转。不规则多边形需要一系列线。

通过让类对其代码和数据负责,可以实现多态性。在这个例子中,每个类都将有自己的Draw()函数,客户端代码只需简单地执行:

shape.Draw()

为了获得任何形状的正确行为,现在的做法是将代码与数据结合在一起,相比之下,旧的做法是代码与数据分离,你需要编写如 drawSquare()drawCircle() 这样的函数。

面向对象、多态和继承都是密切相关的概念,对于了解它们非常重要。在我漫长的职业生涯中,曾经出现过许多所谓的“灵丹妙药”,但是面向对象的范式已经被证明是很好的一个。学习它,理解它,爱上它 - 你会因此受益匪浅 :-)


(a) 我最初把这个写成一个笑话,但它竟然是正确的,因此并不那么好笑。单体苯乙烯由碳和氢组成,C8H8,而聚苯乙烯则由苯乙烯基团组成,(C8H8)n

也许我应该说一个多孔虫是字母 p 的多个实例,尽管现在我不得不解释这个笑话,但它似乎也不好笑。

有时候,你应该趁着自己还没有输得太惨就放弃 :-)


23
多态性与面向对象编程(OOP)无关,但是OOP与多态性有关,因为它本质上支持多态性(假设它是一种良好的OOP语言)。查看函数式编程以获取其他多态性示例。 - alternative
10
这两行对我非常有帮助:Poly表示“许多”,Morph表示“改变或形态”。 - Jo Smo
4
Polyp的缩写为polypo(u)s。而pous在希腊语中意为“脚”。;-) - Dirk
2
@Shaun,我认为你可能过于字面/狭义地使用“接口”这个术语了——我是指它作为英语术语而不是某种任意计算机语言的特定定义。它并不意味着具有完全相同参数的完全相同功能,它只是一种以相同方式影响“对象”的方法,而不考虑它们的基础具体类型。这可以包括使用不同参数类型的方法重载以及更纯粹的多态形式。 - paxdiablo
3
关于你的编辑:“解释一个笑话就像解剖一只青蛙。你会更好地理解它,但青蛙在这个过程中死了。”- E.B.怀特 - Aaron
显示剩余10条评论

297

多态性是指当您将对象视为某个通用版本时,但在访问它时,代码确定它的确切类型并调用关联的代码。

这里有一个C#的示例,在控制台应用程序中创建四个类:

public abstract class Vehicle
{
    public abstract int Wheels;
}

public class Bicycle : Vehicle
{
    public override int Wheels()
    {
        return 2;
    }
}

public class Car : Vehicle
{
    public override int Wheels()
    {
        return 4;
    }
}

public class Truck : Vehicle
{
    public override int Wheels()
    {
        return 18;
    }
}
现在,在控制台应用程序的Main()模块中创建以下内容:
public void Main()
{
    List<Vehicle> vehicles = new List<Vehicle>();

    vehicles.Add(new Bicycle());
    vehicles.Add(new Car());
    vehicles.Add(new Truck());

    foreach (Vehicle v in vehicles)
    {
        Console.WriteLine(
            string.Format("A {0} has {1} wheels.",
                v.GetType().Name, v.Wheels));
    }
}

在这个例子中,我们创建了一个基类 Vehicle 的列表,它不知道每个子类具有多少个轮子,但知道每个子类负责知道它有多少个轮子。然后,我们将自行车、汽车和卡车添加到列表中。接下来,我们可以循环遍历列表中的每个 Vehicle,并对它们进行相同的处理,但是当我们访问每个 Vehicle 的“Wheels”属性时,Vehicle 类将执行该代码的委派给相关的子类。这段代码被称为多态,因为在运行时被引用的子类决定要执行的确切代码。希望这能帮助你。

6
我认为这是一个非常好的例子,清晰地展示了父接口的概念,只有在实例化对象时才需要具体版本,比如车辆和汽车。 - wired00
1
我认为这是最清晰的例子,但如果你是PHP程序员,可能先查看此链接会更容易,然后再查看这个链接:https://code.tutsplus.com/tutorials/solid-part-4-the-dependency-inversion-principle--net-36872 - Oliver Williams
此外(超出了原始问题的范围),需要注意我们将分析限制在已知对象上,我们不会传递一个对象(如导入文件),然后确定它是什么类型(Excel,CSV,YAML,SQL 等等)。要实现这一点,需要编写某种工厂类来调用 Class_ExcelClass_CSV,或者使用 Reader 类。无论哪种方式,都需要在某个地方存储某种迭代 if/then/else。 - Oliver Williams
@Antony Gibbs:这是一个非常好的例子(一系列通用类型),对我来说很有意义...否则,如果每个类都有自己的轮子函数而不继承基类,那有什么大不了的呢?除了列表之外,还有哪些概念适合多态? - T.T.T.

211

来自《理解和应用PHP中的多态性》,感谢Steve Guidetti。

多态性是一个非常简单的概念,却用了一个很长的词来形容它。

在面向对象编程中,多态性描述了一种模式,即不同类在共享相同接口的同时具有不同的功能。

多态性的优点在于处理不同类的代码不需要知道它使用的是哪个类,因为它们都以相同的方式使用。多态性在现实世界中的类比是一个按钮。每个人都知道如何使用按钮:只需施加压力。然而,按钮“做什么”取决于它所连接的东西及其使用的上下文——但结果不影响它的使用方式。如果你的老板让你按一个按钮,那么你已经拥有执行任务所需的所有信息。

在编程世界中,多态性可用于使应用程序更加模块化和可扩展。与其使用混乱的条件语句来描述不同的操作,不如创建可互换的对象,并根据需要进行选择。这是多态性的基本目标。


14
按钮比喻不是更多地与抽象概念相关联吗? - thewpfguy
6
好的,请让我知道需要翻译的内容。 - Mantriur
8
@Mantriur: 这确实是剽窃,我们有规则反对此行为:http://stackoverflow.com/help/referencing。但考虑到它的得分和旧帖子在回答删除时免受声誉损失,我不确定现在直接删除它是否会有所改善。下一个最好的选择是代表用户编辑归属信息,尽管我强烈认为用户应该在自己的答案中引用来源。 - BoltClock
1
我认为暗示多态性只适用于类和/或面向对象编程是不正确的,因为临时多态或参数多态并不一定需要类和/或对象。我认为这个答案所讨论的是子类型化(也称为子类型多态或包含多态)。 - StubbornShowaGuy

68

如果有人对这些人说“Cut”

  1. 外科医生
  2. 发型师
  3. 演员

会发生什么?

  • 外科医生会开始切口。
  • 发型师会开始理发。
  • 演员会突然停止当前场景的表演,等待导演指示。

因此,上面的表示展示了OOP中的多态性(相同名称,不同行为)。

如果您去面试,面试官要求您在我们坐在同一房间的情况下讲述/展示多态性的实时示例,请说-

答案-门/窗户

想知道为什么吗?

通过门/窗户 - 一个人可以进来,空气可以进来,光线可以进来,雨水可以进来等等。

为了更好地理解并以简单的方式进行说明,我使用了以上的例子。 如果需要代码参考,请参考以上答案。


9
我认为这并不是一个好的例子,因为它可能导致经验不足的人认为,如果两个类有一个 .foo() 方法,那么它们应该共享一个公共接口。然而,这不是正确的抽象,会导致错误的抽象。一个接口应该定义一个要扮演的角色,这个角色可以有许多不同的实现,但所有的实现都从相同的输入集合中获取数据,并返回相同的输出集合中的内容。对于外科医生、美发师或演员来说,x.cut(...) 的输入都不相同,输出也不相同。 - Matt Klein

44

类比简单解释

美国总统采用多态性。怎么做呢?他有很多顾问:

  1. 军事顾问
  2. 法律顾问
  3. 核物理学家(作为顾问)
  4. 等等。

总统不是镀锌或量子物理学的专家。他不知道很多东西。他也不需要知道。他利用多态性方法来治理:

总统所做的就是要求人们给他建议。他的顾问们都会以不同的方式回应,但他们都知道总统的意思是:Advise()

public class MisterPresident
{
    public void RunTheCountry()
    {
        // assume the Petraeus and Condi classes etc are instantiated.
        petraeus.Advise(); // # Petraeus says send 100,000 troops to Fallujah
        condolezza.Advise(); // # she says negotiate trade deal with Iran
        healthOfficials.Advise(); // # they say we need to spend $50 billion on ObamaCare
    }
}

这种方法使得总统可以在不了解军事、医疗保健或国际外交等方面的任何知识的情况下,实际上运行国家:细节留给专家处理。总统唯一需要知道的是要求人们“Advise()”。

你不想要的:

public class MisterPresident
{
    public void RunTheCountry()
    {
        // people walk into the Presidents office and he tells them what to do
        // depending on who they are.

        // Fallujah Advice - The President tells his military exactly what to do.
        petraeus.IncreaseTroopNumbers();
        petraeus.ImproveSecurity();
        petraeus.PayContractors();

        // Condi diplomacy advice - Prez tells Condi how to negotiate

        condi.StallNegotiations();
        condi.LowBallFigure();
        condi.FireDemocraticallyElectedIraqiLeaderBecauseIDontLikeHim();

        // Health care

        healthOfficial.IncreasePremiums();
        healthOfficial.AddPreexistingConditions();
    }
}

不!不!不!在上述情况下,总统在做所有的工作。他告诉他的顾问们该做什么,而不是相反。总统了解增加部队数量和现有条件。这意味着如果政策变化,那么总统就必须改变他的命令,以及佩特雷乌斯等级。

我们只需要改变佩特雷乌斯等级,因为总统不应该陷入那种细节之中。他只需知道,如果他下达一个命令,一切都会得到妥善处理。所有的细节应该交给专家来处理。

这使得总统能够发挥自己最擅长的:制定总体政策,出风头和打高尔夫:P。

它是如何实现的 - 通过基类还是公共接口

从本质上讲,这就是多态性。

具体是如何实现的?通过"实现一个公共接口"或者使用一个基类(继承)- 参见上面的回答,这些回答更清楚地详细说明了这一点。(要更清楚地理解这个概念,您需要知道什么是接口,以及您需要理解什么是继承。没有这些,您可能会有困难。)

换句话说,Petraeus、Condi和HealthOfficials都将是实现一个接口的类——我们称之为接口,它只包含一个方法:。但现在我们正在进入具体细节。
这将是理想的情况。
    public class MisterPresident
    {
            // You can pass in any advisor: Condi, HealthOfficials,
            //  Petraeus etc. The president has no idea who it will 
            // be. But he does know that he can ask them to "advise" 
            // and that's all Mr Prez cares for.

        public void RunTheCountry(IAdvisor governmentOfficer)
        {             
            governmentOfficer.Advise();              
        }
    }


    public class USA
    {
        MisterPresident president;

        public USA(MisterPresident president)
        {
            this.president = president;
        }

        public void ImplementPolicy()
        {
            IAdvisor governmentOfficer = getAdvisor(); // Returns an advisor: could be condi, or petraus etc.
            president.RunTheCountry(governmentOfficer);
        }
    }

摘要

你真正需要知道的就是:

  • 总统不需要知道具体细节 - 这些留给其他人。
  • 总统只需要知道,无论谁走进门来给他提建议 - 我们知道,当被要求提供建议时,他们绝对知道该怎么做(因为实际上他们都是顾问,或者说是IAdvisors :))

希望能对你有所帮助。如果有任何不明白的地方,请发表评论,我会再次尝试解释。


6
太棒的例子!谢谢。 - Iman Sedighi
3
非常有趣的比喻。谢谢你。 - TNg
2
@T.T.T. 因为 (1) 每次你有一个新的顾问,那么你就必须更改总统类 - 你不想做两次修改,只需要一次。 (2) 其次,如果你必须更改现有的顾问,那么你可能必须返回并更改总统类中的这三个调用之一 - 你只想做一次更改,而不是两次。 (3) 如果你有三个单独的调用,那么你将不得不在总统类内询问:“如果是健康顾问?那么就这样做:”和“如果是彼得劳斯,那么就那样做等等。”这种模式需要重复,这是不必要和复杂的。请参见上面的编辑。 - BenKoshy
1
@T.T.T. 是的,你说得对。但我必须慢慢向读者介绍这个概念,否则他们就无法理解。我已经进行了进一步的修改。如果需要澄清,请告知。 - BenKoshy
1
@T.T.T. 基类与接口是程序员在进行多态性时必须做出的设计决策。请参阅此处以获取更多详细信息:https://dev59.com/onVD5IYBdhLWcg3wL4mM - BenKoshy
显示剩余3条评论

28

多态性是指将一个对象类视为其父类的能力。

例如,假设存在一个名为Animal的类和一个继承自Animal的名为Dog的类。多态性是指可以像这样将任何Dog对象视为Animal对象:

Dog* dog = new Dog;
Animal* animal = dog;

我想知道这与@Ajay Patel所提供的“类具有不同的功能,同时共享一个公共接口”的流行解释有何关系。 - BornToCode
1
@BornToCode 父类是/提供了共同的接口。 - grokmann
2
多态性不需要子类型化。 - Shaun Luttin

26

多态性:

它是面向对象编程的概念。不同对象对相同消息做出各自不同响应的能力称为多态性。

多态性源于每个类都存在于其自己的命名空间中。类定义内分配的名称不会与任何外部分配的名称冲突。这对于对象数据结构中的实例变量和对象方法都是正确的:

  • 就像 C 结构的字段位于受保护的命名空间中一样,对象的实例变量也是如此。

  • 方法名称也是受保护的。与 C 函数的名称不同,方法名称不是全局符号。一个类中的方法名称不能与其他类中的方法名称发生冲突;两个非常不同的类可以实现具有相同名称的方法。

方法名称是对象接口的一部分。当发送请求对象执行某些操作的消息时,消息指定了对象应该执行的方法。由于不同的对象可以具有相同名称的方法,因此必须相对于接收消息的特定对象理解消息的含义。发送到两个不同对象的相同消息可以调用两个不同的方法。

多态性的主要好处在于简化了编程接口。它允许建立约定,这些约定可以在类之间重复使用。不必为程序中添加的每个新函数发明新名称,可以重复使用相同的名称。编程接口可以被描述为一组抽象行为,与实现它们的类完全无关。

例子:

例子-1: 这是一个简单的例子,用 Python 2.x 写成。

class Animal:
    def __init__(self, name):    # Constructor of the class
        self.name = name
    def talk(self):              # Abstract method, defined by convention only
        raise NotImplementedError("Subclass must implement abstract method")

class Cat(Animal):
    def talk(self):
        return 'Meow!'

class Dog(Animal):
    def talk(self):
        return 'Woof! Woof!'

animals = [Cat('Missy'),
           Dog('Lassie')]

for animal in animals:
    print animal.name + ': ' + animal.talk()

例子-2:多态性在Java中通过方法重载和方法覆盖概念实现。

考虑汽车示例以讨论多态性。选择任何品牌,如Ford、Honda、Toyota、BMW、Benz等,它们都是Car类型。

但是,每个品牌都有自己的高级特性和更先进的技术,涉及其移动行为。

现在让我们创建一个基本类型的Car。

Car.java

public class Car {

    int price;
    String name;
    String color;

    public void move(){
    System.out.println("Basic Car move");
    }

}

让我们实现福特汽车的例子。

福特扩展了类型Car以继承其所有成员(属性和方法)。

Ford.java

public class Ford extends Car{
  public void move(){
    System.out.println("Moving with V engine");
  }
}

上述的 Ford 类继承了 Car 类并实现了 move() 方法。尽管通过继承,move 方法已经可用于 Ford,但 Ford 仍然以自己的方式实现了该方法。这称为方法重写。

Honda.java

public class Honda extends Car{
  public void move(){
    System.out.println("Move with i-VTEC engine");
  }
}

就像福特(Ford)一样,本田(Honda)也扩展了汽车类型并以自己的方式实现了移动方法。

方法重写是实现多态性的一个重要特征。使用方法重写,子类型可以改变通过继承可用的方法的工作方式。

PolymorphismExample.java

public class PolymorphismExample {
  public static void main(String[] args) {
    Car car = new Car();
    Car f = new Ford();
    Car h = new Honda();

    car.move();
    f.move();
    h.move();

  }
}

多态性示例输出:

在 PolymorphismExample 类的 main 方法中,我创建了三个对象 - Car、Ford 和 Honda。这三个对象都是由 Car 类型引用的。

请注意这里一个重要的点:超类类型可以引用子类类型的对象,但反之则不行。原因是通过继承,超类的所有成员都可用于子类,并且在编译时,编译器尝试评估我们使用的引用类型是否具有他正在尝试访问的方法。

因此,在 PolymorphismExample 中的 car、f 和 h 引用中,move 方法存在于 Car 类型中。因此,编译器在编译过程中通过而没有任何问题。

但是当涉及到运行时执行时,虚拟机会调用子类型对象上的方法的实现。因此,所有的对象都是 Car 类型,但在运行时,执行取决于调用发生的对象。这就是所谓的多态性。


重载的概念与继承和多态无关。 - srk
@srk 方法重载是实现多态性的一种方式。它通常被归类为静态或特殊多态性。http://wiki.c2.com/?CategoryPolymorphism - Shaun Luttin

13

通常这指的是类型A的对象表现得像类型B的对象一样的能力。在面向对象编程中,通常通过继承来实现。以下是一些维基百科链接,供进一步阅读:

编辑:修复了链接错误。


10
“一个类型为A的对象表现得像一个类型为B的对象的能力”- 这不是准确的定义。我会说更像是把类型为A的对象当作类型为B的对象来处理的能力。 - Artem Barger
是的。也许这样措辞更好。 - JesperE
为了完整性,许多编程语言通过鸭子类型来实现多态,例如Python。 - ilya n.
我想知道这与@Ajay Patel所提供的“类具有不同的功能,同时共享一个公共接口”的流行解释有何关系。 - BornToCode

9

多态是这样的:

class Cup {
   int capacity
}

class TeaCup : Cup {
   string flavour
}

class CoffeeCup : Cup {
   string brand
}

Cup c = new CoffeeCup();

public int measure(Cup c) {
    return c.capacity
}

您可以传递一个杯子而不是特定的实例。这有助于通用性,因为您不必为每种杯子类型提供特定的measure()实例。


3
这是特定的子类型多态性。 - Shaun Luttin
@vinko-vrsalovic:在美国农村地区,软件开发是什么样的? - T.T.T.

8

我正在浏览另一篇关于完全不同主题的文章…… 然后“多态性”出现了…… 现在我以为我知道什么是多态性了…… 但显然并不是这个美妙的解释方式…… 想把它写下来放在某个地方…… 更好的是我会分享它……

http://www.eioba.com/a/1htn/how-i-explained-rest-to-my-wife

从这部分继续阅读:

..... 多态性。 这是说不同的名词可以被应用相同的动词的极客方式。


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