如何以易于理解的方式描述多态性?
我们可以在 Internet 和书籍上找到很多关于该主题的信息,例如Type polymorphism。但是,让我们尽量让它变得简单。
如何以易于理解的方式描述多态性?
我们可以在 Internet 和书籍上找到很多关于该主题的信息,例如Type polymorphism。但是,让我们尽量让它变得简单。
两个对象以不同的行为响应相同的消息;发件人不必关心。
每个带有简单易拉盖的罐头都以相同的方式打开。
作为人类,您知道您可以找到任何类似的 Open()。
打开时,并非所有罐头的行为方式都相同。
有些含有坚果,有些含有冒出来的假蛇。
结果取决于罐的类型,如果罐是“CanOfNuts”或“CanOfSnakes”,但这与您如何打开它无关。你只知道你可以打开任何罐子,并且会得到某种结果,这取决于你打开的是什么类型的罐子。
pUnlabledCan->Open(); //可能给坚果,可能给蛇。我们不知道,直到我们称之为
Open() 具有“Contents”的通用返回类型(或者我们可能决定没有返回类型),因此 open 始终具有相同的函数签名。
你,人类,是用户/呼叫者。
Open() 是虚拟/多态函数。
“Can”是抽象基类。
CanOfNuts 和 CanOfSnakes 是“Can”类的多态子类。
每个 Can 都可以打开,但它具体做什么以及它返回的具体内容类型取决于它是什么类型的 can。
当您看到 pUnlabledCan 时,您只知道您可以 Open() 它,它会返回内容。任何其他行为(例如在你的脸上弹出蛇)都是由特定的 Can 决定的。
这是来自我对类似问题的回答。这是伪 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() 方法的特定实现行为。
对多态性最简单的描述是它是一种减少 if/switch 语句的方法。
它还有一个好处是允许您在不修改现有类的情况下扩展您的 if/switch 语句(或其他人的语句)。
例如,考虑Stream
.NET 中的类。如果没有多态性,它将是一个庞大的类,其中每个方法都实现一个 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.
}
}
相反,我们允许运行时以更有效的方式为我们进行切换,通过自动选择基于具体类型 ( FileStream
, NetworkStream
) 的实现,例如
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
}
}
Poly: many
Morphism: 形式/形状
演员与角色(或角色)
苹果和橙子都是水果。水果可以吃。因此,苹果和橙子都可以吃。
踢球者?你吃的不一样!你剥橙子,但不剥苹果。
所以实现不同,但最终结果是一样的,你吃水果。
如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么你可以在任何需要鸭子的地方把它当作鸭子对待。
多态性允许对象“看起来”相同,但行为方式不同。常见的例子是使用一个带有 Speak() 方法的动物基类,狗子类会发出 Bark,而 Pig 子类会发出 oink。
大多数人使用的 5 秒简短答案,以便其他开发人员可以了解多态性是重载和覆盖
相同的语法,不同的语义。
最简单的描述方式:一个动词,可以应用于不止一种对象。
正如希勒尔所说,其他一切都只是评论。
多态性通过依赖于一个共同的“父母”的知识来抽象地处理事物(想想像动物这样的层次结构作为狗和猫的父母)。
例如,所有动物都可以呼吸氧气,虽然它们的呼吸方式可能不同,但您可以设计一个为动物提供呼吸氧气的设施,同时支持狗和猫。
作为额外的一点,即使 Animal 是一个“抽象”标识符,您也可以这样做(没有真正的“Animal”事物,只是动物的类型)。
多态性是将多于一种类型的值存储在单一类型的位置。
请注意,在我撰写本文时,这个问题的大多数其他答案实际上都是在描述动态调度,而不是多态性。
动态调度需要多态性,但反之则不然。可以想象一种与 Java 或 C# 非常相似但其 System.Object 没有成员的语言;在对值进行任何操作之前,必须进行类型转换。在这种概念语言中,会有多态性,但不一定是虚拟方法或任何其他动态调度机制。
动态调度是相关但独特的概念,在大多数其他答案中都得到了很好的描述。但是,它通常在面向对象语言中的工作方式(根据第一个(“this”或“Self”)参数类型选择函数)并不是它可以工作的唯一方式。多重分派也是可能的,其中选择应用于所有参数的类型。
类似地,重载决议和多重分派是彼此完全相似的。重载决议是应用于静态类型的多重分派,而多重分派是应用于存储在多态位置中的运行时类型的重载决议。
当相同的方法应用于多个类时,您会得到多态性。例如,字符串和列表都可能具有“反向”方法。两种方法具有相同的名称(“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()。
编辑:简单!=简短,恕我直言
多态性是根据公共属性将世界划分为多个盒子,当您只想使用这些公共属性时,将给定盒子中的项目视为可互换的。
多态性是通过在它们之间建立共享身份然后利用它来将不同的事物视为同一事物的能力。
多态性是一种语言功能,允许高级算法代码对多种类型的数据进行不变的操作。
这是通过确保操作为每种数据类型调用正确的实现来完成的。即使在 OOP 上下文中(根据这个问题的标签),这个“正确的实现”也可以在编译时或运行时解决(如果您的语言同时支持)。在像 C++ 这样的一些语言中,编译器提供的对运行时多态性(即虚拟分派)的支持是面向对象编程的,而其他类型的多态性也可以对不是对象的数据类型(即不是struct
或class
实例,但可能是内置类型,如int
or double
)。
(我的回答中列出并对比了 C++ 支持的多态类型:C++ 中的多态- 即使您编写其他语言,它也可能具有指导意义)
我尝试和思考的方式看起来相同,但根据实例可能具有不同的功能。所以你可以有一个类型
interface IJobLoader
但取决于它的使用方式,它可能具有不同的功能,但看起来仍然相同。您可能有 BatchJobLoader、NightlyJobLoader 等实例
也许我已经走远了。
术语多态性也适用于重载函数。例如,
string MyFunc(ClassA anA);
string MyFunc(ClassB aB);
是多态的一个非面向对象的例子。
这是一种处理不同事物的方法,这些事物可以以相同的方式做类似的事情,而无需关心它们是如何做的。
假设您有一个游戏,其中有一堆不同类型的车辆在四处行驶,例如汽车、卡车、滑板、飞机等……它们都可以停止,但每辆车的停止方式不同。有些车辆可能需要降档,有些车辆可能会冷停。多态性让您可以做到这一点
foreach (Vehicle v in Game.Vehicles)
{
v.Stop();
}
停止的实现方式推迟到不同的车辆,因此您的程序不必关心它。
是对象必须以不同方式响应同一消息的能力。
例如,在 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# 等语言中,为了将对象分配给对象引用,它们必须通过实现接口或作为公共类的子类来共享相同的类型层次结构。
容易..简单。
到目前为止,多态是面向对象编程中最重要和最相关的特性。
调用新代码只是一种让老冷的方法。您编写了一些应用程序,该应用程序接受一些“Shape”接口以及其他人必须实现的方法(例如 - getArea)。如果有人想出一种新的绝妙方法来实现该接口,您的旧代码可以通过 getArea 方法调用该新代码。
某种类型的对象(例如汽车)像另一种类型(例如车辆)一样行动(例如制动)的能力,这通常表明在类型层次结构中的某个点有共同的祖先(例如汽车是车辆的子类型) .
多态性是面向对象的解决方案,用于解决将函数传递给另一个函数的问题。在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