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

637

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


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

2

多态字面上意思是多种形式。不同类的对象有相同名称的方法,但工作流程不同。 一个简单的例子是:

考虑一个人X。

他只是一个人,但可以扮演多种角色。 你可能会问如何实现:

他是他母亲的儿子。 他是他朋友的朋友。 他是他姐姐的兄弟。


2

面向对象编程中的多态性(Polymorphism),意味着一个类可以有不同的类型,继承是实现多态性的一种方式。

例如,Shape 是一个接口,它有 SquareCircleDiamond 三个子类型。现在你有一个 Square 对象,你可以自动将 Square 向上转型为 Shape,因为 Square 是 Shape。但是,当你尝试将 Shape 向下转型为 Square 时,必须进行显式的类型转换,因为你不能说 Shape 就是 Square,它也可能是 Circle。 所以你需要手动使用代码进行强制类型转换,例如:Square s = (Square)shape,如果 shape 是 Circle,你将得到 java.lang.ClassCastException 异常,因为 Circle 不是 Square。


2

什么是多态性?

多态性是指能够:

  • 通过只知道其一般类型而调用特定类型的方法来调用专门类型实例上的操作:

    这是动态多态性

  • 定义具有相同名称但具有不同参数的多个方法:

    这是静态多态性

第一个是历史定义,也是最重要的定义。

多态性有什么用处?

它允许创建类层次结构的强类型一致性,并进行一些“神奇”的事情,比如管理不同类型对象的列表而不知道它们的类型,只知道它们的父类型之一,以及数据绑定。

强类型和弱类型

示例

这里有一些形状,如点、线、矩形和圆,具有 Draw() 操作,可以接受没有参数或者一个参数来设置超时时间来擦除它。

public class Shape
{
 public virtual void Draw()
 {
   DoNothing();
 }
 public virtual void Draw(int timeout)
 {
   DoNothing();
 }
}

public class Point : Shape
{
 int X, Y;
 public override void Draw()
 {
   DrawThePoint();
 }
}

public class Line : Point
{
 int Xend, Yend;
 public override Draw()
 {
   DrawTheLine();
 }
}

public class Rectangle : Line
{
 public override Draw()
 {
   DrawTheRectangle();
 }
}

var shapes = new List<Shape> { new Point(0,0), new Line(0,0,10,10), new rectangle(50,50,100,100) };

foreach ( var shape in shapes )
  shape.Draw();

这里应该将Shape类和Shape.Draw()方法标记为抽象。

它们的作用不是为了让人理解。

说明

在没有多态性的情况下,使用抽象-虚拟-重写,当解析形状时,只调用Spahe.Draw()方法,因为CLR不知道调用哪个方法。所以它调用我们所操作的类型的方法,这里由于列表声明,类型是Shape。所以代码根本不做任何事情。

有了多态性,CLR能够使用称为虚表的东西推断我们所操作的对象的真实类型。所以它调用“好”的方法,在这里如果Shape是Point,则调用Point.Draw()而不是Shape.Draw()。所以代码绘制形状。

更多阅读

C# - 多态性(1级)

Java中的多态性(2级)

多态性(C#编程指南)

虚方法表


1

多态性是指在一个给定的类中使用对象的能力,其中组成该对象的所有组件都被该类的子类继承。这意味着,一旦声明了该对象,所有位于其下方的子类(以及它们的子类,依此类推,直到达到最远/最低的子类)都会继承该对象及其组件。

请记得每个类必须保存在单独的文件中。

以下代码举例说明了多态性:

超类:

public class Parent {
    //Define things that all classes share
    String maidenName;
    String familyTree;

    //Give the top class a default method
    public void speak(){
         System.out.println("We are all Parents");
    }
}

父类,一个子类:
public class Father extends Parent{
    //Can use maidenName and familyTree here
    String name="Joe";
    String called="dad";

    //Give the top class a default method
    public void speak(){
        System.out.println("I am "+name+", the father.");
    }
}

孩子,另一个子类:

public class Child extends Father {
    //Can use maidenName, familyTree, called and name here

    //Give the top class a default method
    public void speak(){
        System.out.println("Hi "+called+". What are we going to do today?");
    }
}

执行方法,引用父类开始:
public class Parenting{
    public static void main(String[] args) {
        Parent parents = new Parent();
        Parent parent = new Father();
        Parent child = new Child();

        parents.speak();
        parent.speak();
        child.speak();
    }
}

请注意每个类都需要在单独的*.java文件中声明。 代码应该编译通过。 此外,请注意您可以在下面继续使用maidenName和familyTree。 这就是多态的概念。 还探讨了继承的概念,其中一个类可以被子类使用或进一步定义。

希望这有所帮助并使其清晰明了。 我会在找到一台可以用来验证代码的计算机后发布结果。感谢您的耐心等待!


2
请注意,每个孩子都不是父母,因此这种结构是错误的。如果您不是从“人”开始,顶级类应该是Child,这总是正确的,除了Adam。您可以将他的parent_id设置为null,因为创造者不能用任何人类智慧的构造来定义。 - Yehosef

1

多态允许相同的例程(函数、方法)对不同的类型进行操作。

由于许多现有答案将子类型与多态混淆在一起,因此这里列举了三种实现多态的方式(包括子类型)。

  • 参数化(通用)多态允许例程除普通参数外接受一个或多个类型参数,并在这些类型上运行自己。
  • 子类型多态允许例程对其参数的任何子类型进行操作。
  • 特殊多态通常使用例程重载来授予多态行为,但也可以指其他多态实现。

另请参见:

http://wiki.c2.com/?CategoryPolymorphism

https://en.wikipedia.org/wiki/Polymorphism_(computer_science)


1
首先,我认为多态是面向对象编程的重要组成部分,它使我们能够定义多个类共享的行为,但可以针对每个类单独更改。我想分享一些我的经验,以帮助在简单的示例中降低代码复杂度。
我可以理解,在某些方面,这可能有助于重用代码并保持可维护性。它使它们变得不那么痛苦。但是,像任何其他编程方法一样,有时候多态可能不是最佳选择。
考虑一个名为“Car”的基类,其中包含一个名为“StartEngine()”的方法,该方法告诉引擎如何启动。假设您有派生类,例如“MercedesBenzCar”和“TeslaModelSCar”。在这种情况下,您可以使用多态来定义基本“Car”类中的“StartEngine()”方法,然后在派生类中覆盖该方法,以为每种类型的汽车提供特定的实现。但是这可能会让您陷入麻烦。让我们来看看一些代码。
/// <summary>
///     Base class for car objects
/// </summary>
public abstract class Car
{
    public virtual void StartEngine()
    {
        Console.WriteLine(value: "Car engine has been started.");
    }
    
    public virtual void StopEngine()
    {
        Console.WriteLine(value: "Car engine has been stopped.");
    }
}

一旦我们定义了基类,让我们定义派生类。

/// <summary>
///     Example of polymorphism in C# using abstract classes on Mercedes Benz cars
/// </summary>
public class MercedesBenzCar : Car
{
    public override void StartEngine()
    {
        Console.WriteLine(value: "Turning on the ignition and starting the Mercedes-Benz S Class...");
    }

    public override void StopEngine()
    {
        Console.WriteLine(value: "Turning off the ignition and stopping the Mercedes-Benz S Class...");
    }
}

/// <summary>
///     Example of polymorphism in C# using abstract classes on Tesla Electric Cars
/// </summary>
public sealed class TeslaModelSCar : Car
{
    public override void StartEngine()
    {
        Console.WriteLine(value: "The electric motor in the Tesla Model S car was activated...");
    }

    public override void StopEngine()
    {
        Console.WriteLine(value: "The electric motor in the Tesla Model S car was deactivated...");
    }
}

那么这有什么意义呢?在这个例子中,基类 CarStartEngine()StopEngine() 方法展示了如何启动和停止汽车。MercedesBenzCarTeslaModelSCar 类重写这些方法,以提供各自独特的电动和燃油汽车行为实现。因此,直接在派生类中定义行为比在基类中使用多态性来描述更有意义。

正如您所看到的,如果每个派生类中的 StartEngine()StopEngine() 方法表现非常不同,那么这种设计可能不是一个好主意。这是因为在基类中定义有意义的 StartEngine()StopEngine() 方法的实现需要大量工作。在这种情况下,直接在派生类中定义 StartEngine()StopEngine() 方法而不是使用多态性可能更好。

我们如何解决这个问题呢?

让我们再次看一下这个例子,看看当派生类的行为非常不同时,如何在C#中使用继承和多态。

关于重构,我想建议引入接口或合同作为解决此问题的潜在方法,并为我们提供更好的设计。在谈论接口之前,让我们先了解它们是什么。接口可以被视为对描述其属性和方法的类或结构的提案。

C#的新版本可能会有默认实现,但让我们不要把事情弄得更难。如果您想了解更多信息,请查看link。简而言之,它们说明成员签名应该是什么样子的,但不说明实现应该是什么样子的。

我想提议 ICar 接口定义两个方法,StartEngine()StopEngine()MercedesBenzCarTeslaModelSCar 类应该实现 ICar 接口,这意味着我们可以摆脱抽象类并将其转换为接口。 因此,Car 应该变成 ICar

为什么?这比继承更灵活和可持续,因为您可以从类中添加或删除 ICar 接口,而不影响类继承层次结构。 现在让我们重构上面的解决方案以实践它。

/// <summary>
///     Base interface for all car types.
/// </summary>
public interface ICar
{
    /// <summary>
    ///     Use this method to turn on the car engine.
    /// </summary>
    void StartEngine();

    /// <summary>
    ///     Use this method to turn off the car engine.
    /// </summary>
    void StopEngine();
}

一旦接口ICar被实现,现在是时候重构具体类了。
/// <summary>
///     Example of using the interface ICar for the class TeslaModelSCar.
/// </summary>
public sealed class TeslaModelSCar : ICar
{
    public void StartEngine()
    {
        Console.WriteLine(value: "The electric motor in the Tesla Model S car was activated...");
    }

    public void StopEngine()
    {
        Console.WriteLine(value: "The electric motor in the Tesla Model S car was deactivated...");
    }
}

/// <summary>
///     Example of using the interface ICar for the class MercedesBenzCar.
/// </summary>
public class MercedesBenzCar : ICar
{
    public void StartEngine()
    {
        Console.WriteLine(value: "Turning on the ignition and starting the Mercedes-Benz S Class...");
    }

    public void StopEngine()
    {
        Console.WriteLine(value: "Turning off the ignition and stopping the Mercedes-Benz S Class...");
    }
}

重构过程现在已经完成。现在是时候看看如何利用它了。

ICar teslaModelS = new TeslaModelSCar();
ICar mercedesBenz = new MercedesBenzCar();

teslaModelS.StartEngine();
mercedesBenz.StartEngine();

Console.WriteLine(value: "Press any key to stop engines...");
Console.ReadLine();

teslaModelS.StopEngine();
mercedesBenz.StopEngine();

await Task.Delay(delay: TimeSpan.FromSeconds(value: 3)); // Wait for 3 seconds in order to see the output

想象一下,我们的车库里有一堆汽车,并且有另一种方法可以按顺序启动它们的引擎。

var carsInGarage = new ICar[2] { new TeslaModelSCar(), new MercedesBenzCar() };

foreach (var car in carsInGarage)
{
    car.StartEngine();
}

Console.WriteLine(value: "Press any key to stop engines...");
Console.ReadLine();

foreach (var car in carsInGarage)
{
    car.StopEngine();
}

await Task.Delay(delay: TimeSpan.FromSeconds(value: 3)); // Wait for 3 seconds in order to see the output

再说一遍,多态是一种强大的面向对象编程方法,可以让您定义由多个类共享的行为。但是,像任何其他编程方法一样,有时候多态可能不是最佳选择。我相信在以下情况下应该考虑使用接口:

  • 当类的行为与其基类非常不同,并且有很多基于它的类时。
  • 当性能至关重要时。
  • 当您需要支持多个继承,因为C#本身不支持多个继承时。

在决定是否在代码中使用多态时,我认为有必要牢记这些事情。

干杯!


0

0
在编程术语中,多态性是指您的对象可以通过继承等方式存在于多种类型中。如果您创建一个名为“Shape”的类来定义对象具有的边数,则可以创建一个继承它的新类,例如“Square”。当您随后创建“Square”的实例时,您可以根据需要将其从“Shape”转换为“Square”,反之亦然。

0
在面向对象的编程语言中,多态性允许通过相同的接口处理和操作不同的数据类型。例如,在C++中考虑继承:类B派生自类A。可以使用类型为A*(指向类A的指针)的指针来处理类A和类B的对象。

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