尝试尽可能简单地描述多态性[关闭]

Posted

技术标签:

【中文标题】尝试尽可能简单地描述多态性[关闭]【英文标题】:Try to describe polymorphism as easy as you can [closed] 【发布时间】:2010-09-17 15:35:54 【问题描述】:

如何用通俗易懂的方式描述多态性?

我们可以在 Internet 和书籍上找到很多关于该主题的信息,例如 Type polymorphism。但让我们尽量让它变得简单。

【问题讨论】:

...并使其长且易于复制和粘贴。在家工作? ;) 哈哈,不,我的作业离我很远 嗯,很高兴你有幽默感。 请注意:对这个问题的大多数回复都将多态性与第一个参数的动态调度混淆了。如果没有某种动态调度,多态性并不是特别有用,但它是可能的。想想例如一个没有任何成员的概念 Java 对象,总是需要强制转换。 Howz that : 一个男孩用 FRIENDSHIP 这个词开始 LOVE,但一个女孩用同一个 FRIENDSHIP 这个词结束 LOVE。Word 相同,但功能不同。这就是 LOL 多态性。 【参考方案1】:

两个对象以不同的行为响应相同的消息;发件人不必关心。

【讨论】:

这应该是公认的答案【参考方案2】:

每个带有简单易拉盖的罐头都以相同的方式打开。 作为人类,你知道你可以 Open() 任何你能找到的东西。

打开后,并非所有罐头的行为方式都相同。 有些罐头包含坚果,有些罐头包含会弹出的假蛇。 结果取决于罐头的类型,如果罐头是“CanOfNuts”或“CanOfSnakes”,但这与您打开它的方式无关。你只知道你可以打开任何罐头,并且会得到某种结果,这取决于你打开的是哪种罐头。

pUnlabledCan->Open(); //可能给坚果,可能给蛇。我们不知道,直到我们称之为它

Open() 具有“Contents”的通用返回类型(或者我们可能决定没有返回类型),因此 open 始终具有相同的函数签名。

您是人类,是用户/呼叫者。 Open() 是虚拟/多态函数。 “Can”是抽象基类。 CanOfNuts 和 CanOfSnakes 是“Can”类的多态子类。 每个罐头都可以打开,但它具体做什么 以及它返回的内容 的具体类型取决于它是什么类型的罐头。 当您看到 pUnlabledCan 时,您只知道您可以 Open() 它,它会返回内容。任何其他行为(例如在你脸上弹出蛇)都是由特定的 Can 决定的。

【讨论】:

【参考方案3】:

这是来自我的answer,来自一个类似的问题。下面是伪 C#/Java 中的多态性示例:

class Animal

    abstract string MakeNoise ();


class Cat : Animal 
    string MakeNoise () 
        return "Meow";
    


class Dog : Animal 
    string MakeNoise () 
        return "Bark";
    


Main () 
   Animal animal = Zoo.GetAnimal ();
   Console.WriteLine (animal.MakeNoise ());

Main() 方法不知道动物的类型,它取决于 MakeNoise() 方法的特定实现行为。

【讨论】:

请注意,您描述的是动态调度(需要多态性),而不是多态性本身。 一个例子不是描述:P 这个例子还不够,因为它们都只返回一个字符串。在我看来,一个最低限度的示例对于每种方法都会有不同的行为...... 我更多是用代码解释而不是文章!【参考方案4】:

对多态最简单的描述是它是一种减少if/switch语句的方法

它还有一个好处是允许您在不修改现有类的情况下扩展您的 if/switch 语句(或其他人的语句)。

例如,考虑 .NET 中的 Stream 类。如果没有多态性,它将是一个庞大的类,其中每个方法都实现一个 switch 语句,例如:

public class Stream

    public int Read(byte[] buffer, int offset, int count)
    
        if (this.mode == "file")
        
            // behave like a file stream
        
        else if (this.mode == "network")
        
            // behave like a network stream
        
        else // etc.
    

相反,我们允许运行时以更有效的方式为我们进行切换,方法是根据具体类型(FileStreamNetworkStream)自动选择实现,例如

public class FileStream : Stream

    public override int Read(byte[] buffer, int offset, int count)
    
        // behave like a file stream
    


public class NetworkStream : Stream

    public override int Read(byte[] buffer, int offset, int count)
    
        // behave like a network stream
    

【讨论】:

我喜欢这个解释,因为它描述了多态性实际上是如何有用的。 动态调度是一种减少 switch/if 语句的方法。使用模式匹配的多重分派和可能为了方便而解构将是一个更灵活的解决方案,并且根本不需要多态性。多态性 != 动态调度。 我并没有说这是减少 if 语句的唯一方法,只是说这是一种方法。这取决于您使用哪种语言,哪种范式更合适。 我喜欢这个解释,但我讨厌真实的编程示例。虽然我确实想知道它们在传达这一点方面是好是坏……【参考方案5】:

聚:很多 态射:形式/形状

【讨论】:

【参考方案6】:

演员与角色(或角色)

【讨论】:

有趣...或者可能是“角色”(而不是角色)。 或者更好的是,一个角色,许多演员。我认为这显然是最好和最简洁的解释——但我可能偏向于一个 bardolate。【参考方案7】:

苹果和橙子都是水果。水果可以吃。因此,苹果和橙子都可以吃。

踢球者?你吃的不一样!你剥橙子,但不剥苹果。

所以实现不同,但最终结果是一样的,你吃水果

【讨论】:

【参考方案8】:

如果它走路像鸭子,叫起来像鸭子,那么你可以在任何需要鸭子的地方把它当作鸭子。

【讨论】:

我认为这更像是对鸭子类型的描述,而不是多态性。这是一种微妙不同的关系(“看起来像一个”而不是“是一个”)。 @Greg 原理是一样的,只是对术语感兴趣。 最好实现IDuck接口 @Wedge - 原理不同。使用多态性,某些东西看起来完全像一只鸭子,但不是一只鸭子。正如我所说,它有细微的不同。 @Greg - 好吧,有一个微妙的区别,但我不认为这是“看起来像一个”与“是一个”。 “is a”意味着继承,而我猜你可以说鸭子类型是“行为就像 a”,我认为这仍然是多态性的一种形式。【参考方案9】:

This is a better article actually

多态性允许对象“看起来”相同,但行为方式不同。通常的例子是使用一个带有 Speak() 方法的动物基类,狗子类会发出 Bark,而 Pig 子类会发出 oink。

大多数人使用的 5 秒简短回答,以便其他开发人员可以理解多态性是重载和覆盖

【讨论】:

【参考方案10】:

语法相同,语义不同。

【讨论】:

【参考方案11】:

最简单的描述方式:一个动词,可以应用于不止一种对象。

正如希勒尔所说,其他一切都只是评论。

【讨论】:

您描述的是动态调度,而不是多态性。 en.wikipedia.org/wiki/Dynamic_dispatch 我不认为这意味着你似乎认为它意味着什么。【参考方案12】:

多态性是通过依赖于一个共同的“父母”的知识来抽象地处理事物(想想像 Animal 作为 Dogs 和 Cats 的父母这样的层次结构)。

例如,所有动物都可以呼吸氧气,虽然它们的呼吸方式可能不同,但您可以设计一个设施,为动物提供呼吸氧气,同时支持狗和猫。

作为额外的一点,即使 Animal 是一个“抽象”标识符,您也可以这样做(没有真正的“Animal”事物,只是动物的类型)。

【讨论】:

【参考方案13】:

多态性是将多种类型的值存储在单一类型的位置。

请注意,在我撰写本文时,这个问题的大多数其他答案实际上是在描述动态调度,而不是多态。

Dynamic dispatch 需要多态性,但反之则不然。可以想象一种与 Java 或 C# 非常相似但其 System.Object 没有成员的语言;在对值进行任何操作之前,必须进行类型转换。在这种概念语言中,会有多态性,但不一定是虚拟方法或任何其他动态调度机制。

动态调度是相关但独特的概念,在大多数其他答案中都得到了很好的描述。但是,它通常在面向对象语言中的工作方式(根据第一个(“this”或“Self”)参数类型选择函数)并不是它可以工作的唯一方式。 Multiple dispatch 也是可能的,其中选择应用于所有参数的类型。

同样,重载解析和多次调度是彼此完全相似的;重载决议是应用于静态类型的多重分派,而多重分派是应用于存储在多态位置中的运行时类型的重载决议。

【讨论】:

协变和逆变是我会使用的术语,而不是动态调度。 协变和逆变又是一个相关但不同的问题;它们是描述一个子类型关系如何相对于另一个变化的比较术语。它们不是特定于多态性的;考虑例如Java 泛型通配符或 CLR 泛型中的协/逆变。 协/逆变就像比较运算符; “这种类型与那个类型是协变的”,有人会说。动态调度是一种机制,当完全指定时,它会做一些事情。 您自己的***链接说“动态调度是在运行时将消息映射到特定代码序列(方法)的过程”。多态性不是运行时技巧。它是由协方差完成的编译时技巧。 当我进一步阅读时,似乎 C++ 的 VTables 在运行时用于选择适当的方法,所以我站得更正......但这看起来很奇怪,因为 C++ 应该能够推断出正确的编译期间的类型。【参考方案14】:

多态性是根据公共属性将世界划分为多个盒子,并在您只想使用这些公共属性时将给定盒子中的项目视为可互换的。

【讨论】:

【参考方案15】:

多态性是通过在它们之间建立共享身份然后利用它来将不同事物视为相同事物的能力。

【讨论】:

对。这也是他们在机场安检中使用剖析的原因……这是另一种概括……【参考方案16】:

当同一个方法应用于多个类时,你会得到多态性。例如,字符串和列表都可能具有“反向”方法。两种方法具有相同的名称(“Reverse”)。两种方法都做了非常相似的事情(反转所有字符或反转列表中元素的顺序)。但是每个“反向”方法的实现都是不同的,并且特定于其类。 (换句话说,String 像字符串一样反转自身,而 List 像列表一样反转自身。)

打个比方,您可以对法国厨师或日本厨师说“做晚餐”。每个人都会以自己独特的方式表演“做晚餐”。

实际结果是您可以创建一个“逆向引擎”,它接受一个对象并在其上调用“逆向”。只要对象具有 Reverse 方法,您的 Reversing Engine 就可以工作。

为了扩展厨师的类比,您可以构建一个“Waiterbot”来告诉厨师“做晚餐”。 Waiterbot 不必知道要制作什么类型的晚餐。它甚至不必确保它正在与厨师交谈。重要的是“厨师”(或消防员、自动售货机或宠物食品分配器)知道在被告知“做晚餐”时该做什么。

作为程序员,这为您带来的是更少的代码行以及类型安全或后期绑定。例如,这是一个类型安全和早期绑定的示例(使用我正在编写的类似 c 的语言):

class BankAccount 
    void SubtractMonthlyFee


class CheckingAccount : BankAccount 

class SavingsAccount : BankAccount 

AssessFee(BankAccount acct) 
    // This will work for any class derived from
    //   BankAccount; even classes that don't exist yet
    acct.SubtractMonthlyFee


main() 

    CheckingAccount chkAcct;
    SavingsAccount saveAcct;

    // both lines will compile, because both accounts
    //   derive from "BankAccount". If you try to pass in
    //   an object that doesn't, it won't compile, EVEN
    //   if the object has a "SubtractMonthlyFee" method.
    AssessFee(chkAcct);
    AssessFee(saveAcct);

这是一个没有类型安全但具有后期绑定的示例:

class DatabaseConnection 
    void ReleaseResources


class FileHandle 
    void ReleaseResources


FreeMemory(Object obj) 
    // This will work for any class that has a 
    //   "ReleaseResources" method (assuming all
    //   classes are ultimately derived from Object.
    obj.ReleaseResources


main() 

    DatabaseConnection dbConn;
    FileHandle fh;

    // You can pass in anything at all and it will
    //   compile just fine. But if you pass in an
    //   object that doesn't have a "ReleaseResources"
    //   method you'll get a run-time error.
    FreeMemory(dbConn);
    FreeMemory(fh);
    FreeMemory(acct); //FAIL! (but not until run-time)

举个很好的例子,看看 .NET ToString() 方法。所有类都有它,因为所有类都派生自 Object 类。但是每个类都可以以对自己有意义的方式实现 ToString()。

编辑:简单!=简短,恕我直言

【讨论】:

【参考方案17】:

多态性是一种语言功能,允许高级算法代码在多种类型的数据上不加改变地运行。

这是通过确保操作为每种数据类型调用正确的实现来完成的。即使在 OOP 上下文中(根据这个问题的标签),这个“正确的实现”也可以在编译时或运行时解决(如果您的语言同时支持)。在 C++ 等一些语言中,编译器提供的对运行时多态性(即虚拟分派)的支持特定于 OOP,而其他类型的多态性也可以对不是对象的数据类型进行操作(即不是 struct 或 @987654323 @ 实例,但可能是内置类型,例如 intdouble)。

(我的回答中列出并对比了 C++ 支持的多态类型:Polymorphism in c++ - 即使您编写其他语言,它也可能具有指导意义)

【讨论】:

【参考方案18】:

我尝试和思考的方式看起来相同,但根据实例可能具有不同的功能。所以你可以有一个类型

interface IJobLoader

但根据使用方式的不同,它可能具有不同的功能,但看起来仍然相同。您可能有 BatchJobLoader、NightlyJobLoader 等实例

也许我已经走了。

【讨论】:

【参考方案19】:

术语多态也适用于重载函数。例如,

string MyFunc(ClassA anA);
string MyFunc(ClassB aB);

是一个非面向对象的多态示例。

【讨论】:

【参考方案20】:

是对象必须以不同方式响应同一消息的能力。

例如,在 smalltalk、Ruby、Objective-C 等语言中,您只需发送消息,它们就会响应。

 dao  = XmlDao.createNewInstance()    #obj 1
 dao.save( data )

 dao = RdbDao.createNewnewInstance()  #obj 2
 dao.save( data )

在此示例中,两个不同的对象以不同的方式响应相同的消息:“createNewInstance() 和 save(obj)”

他们以不同的方式处理相同的信息。在上述语言中,类甚至可能不在同一个类层次结构中,它们响应消息就足够了。

在 Java、C++、C# 等语言中。为了将对象分配给对象引用,它们必须通过实现接口或作为公共类的子类来共享相同的类型层次结构。

简单..简单。

到目前为止,多态是面向对象编程中最重要和最相关的特性。

【讨论】:

【参考方案21】:

这是一种处理不同事物的方法,这些事物可以以相同的方式做类似的事情,而无需关心它们是如何做的。

假设您有一个游戏,其中有一堆不同类型的车辆在四处行驶,例如汽车、卡车、滑板、飞机等......它们都可以停止,但每辆车的停止方式不同。有些车辆可能需要降档,有些可能会冷停。多态性让你可以做到这一点

foreach (Vehicle v in Game.Vehicles)

   v.Stop();

停止的实现方式被推迟到不同的车辆,因此您的程序不必关心它。

【讨论】:

【参考方案22】:

调用新代码只是一种使旧的感冒的方法。您编写了一些应用程序,该应用程序接受一些“Shape”接口以及其他人必须实现的方法(例如 - getArea)。如果有人想出一种新的绝妙方法来实现该接口,您的旧代码可以通过 getArea 方法调用该新代码。

【讨论】:

【参考方案23】:

某种类型的对象(例如汽车)像另一种类型(例如车辆)一样行动(例如制动)的能力,这通常表明在某个时间点有共同的祖先(例如汽车是车辆的子类型)类型层次结构。

【讨论】:

【参考方案24】:

多态是面向对象的解决方案,用于解决将一个函数传递给另一个函数的问题。在C中你可以做

 void h()  float x=3.0; printf("%f", x); 
 void k()  int y=5; printf("%i", y); 
 void g(void (*f)())  f(); 
 g(h);  // output 3.0
 g(k);  // output 5

在 C 语言中,如果函数依赖于附加参数,事情就会变得复杂。如果函数 h 和 k 依赖于不同类型的参数,你就有麻烦了,你必须使用强制转换。您必须将这些参数存储在数据结构中,并将指向该数据结构的指针传递给 g,然后将其传递给 h 或 k。 h 和 k 将指针转换为指向正确结构的指针并解压缩数据。由于可能的转换错误,非常混乱且非常不安全:

 void h(void *a)  float* x=(float*)a; printf("%f",*x); 
 void k(void *a)  int* y=(int*)a; printf("%i",*y); 
 void g(void (*f)(void *a),void *a)  f(a); 
 float x=3.0;
 int y=5;
 g(h,&x); // output x
 g(k,&y); // output y

于是他们发明了多态性。 h 和 k 被提升为类,实际函数被提升为方法,参数是各自类的成员变量,h 或 k。您无需传递函数,而是传递包含所需函数的类的实例。实例包含自己的参数。

class Base  virtual public void call()=0; 
class H : public Base  float x; public void call()  printf("%f",x);  h;
class K : public Base  int y; public void call()  printf("%i",y);  k;
void g(Base &f)  f.call(); ;
h.x=3.0;
k.y=5;
g(h); // output h.x
g(k); // output k.x

【讨论】:

以上是关于尝试尽可能简单地描述多态性[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

方法重载是不是被认为是多态性? [关闭]

具有多态模板参数的多态模板类

多态性的好处[关闭]

继承 封装 多态 简单介绍

多态性 - 仅用两个句子定义 [关闭]

测试具有许多站点的数据集中的正态性[关闭]