Javascript中的多态性是什么?


90

我读过一些我可以在互联网上找到的关于多态的文章。但是我认为我不太了解它的含义及其重要性。大多数文章都没有说明为什么它很重要,以及如何在OOP中实现多态行为(当然是在JavaScript中)。

我无法提供任何代码示例,因为我不知道如何实现它,因此我的问题如下:

  1. 它是什么?
  2. 我们为什么需要它?
  3. 这个怎么运作?
  4. 如何在javascript中实现这种多态行为?

我有这个例子。但是很容易理解该代码的结果。它没有对多态性本身给出任何清晰的想法。

function Person(age, weight) {
    this.age = age;
    this.weight = weight;
    this.getInfo = function() {
        return "I am " + this.age + " years old " +
        "and weighs " + this.weight +" kilo.";
    }
}
function Employee(age, weight, salary) {
    this.salary = salary;
    this.age = age;
    this.weight = weight;
    this.getInfo = function() {
        return "I am " + this.age + " years old " +
        "and weighs " + this.weight +" kilo " +
        "and earns " + this.salary + " dollar.";
    }
}

Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
  // The argument, 'obj', can be of any kind
  // which method, getInfo(), to be executed depend on the object
  // that 'obj' refer to.

function showInfo(obj) {
    document.write(obj.getInfo() + "<br>");
}

var person = new Person(50,90);
var employee = new Employee(43,80,50000);
showInfo(person);
showInfo(employee);

这个问题可能太广泛了,无法与StackOverflow配合使用。我们能做的最好的就是将您链接到其他多态性解释。StackOverflow最适合回答特定问题或对特定问题的澄清,例如“来源说多态性是XYZ,但是Y是什么意思?”
Vitruvius

2
你不需要它。完全没有 您甚至不需要JS中的类,实际上,还有许多其他的,可以说更好的应用程序构建范例。apply / call / bind消除了对同质性的需求,使用软对象,您可以修改任何内容以适合您的需求,而无需预先装饰它或继承特殊情况。
dandavis,2014年

1
多态不仅与OO相关,而且还有许多含义。您可能想在没有继承的情况下进行多态性问题下阅读其他答案。
Edwin Dalorzo 2014年

继承通常在JavaScript中错误地完成。创建要用作Child原型的Parent实例表明缺乏对构造函数和原型在定义和创建对象中所起的作用的了解。有关更多信息,请参见以下答案: stackoverflow.com/a/16063711/1641941
HMR 2014年

Answers:


99

多态性是面向对象编程(OOP)的宗旨之一。这是设计对象以共享行为并能够用特定行为覆盖共享行为的实践。多态性利用继承的优势来实现这一点。

在OOP中,所有内容都被视为对象。这种抽象可以一直延伸到汽车的螺母和螺栓,也可以简单地扩展到具有年份,品牌和型号的汽车类型。

要具有多态汽车场景,将有基本的汽车类型,然后是子类,这些子类将从汽车继承并在汽车具有的基本行为之上提供自己的行为。例如,一个子类可能是TowTruck,它仍然具有年份和型号,但是可能还具有一些额外的行为和属性,这些行为和属性可能像IsTowing的标志一样基本,甚至与升降机的细节一样复杂。

回到人和雇员的例子,所有雇员都是人,但所有人不是雇员。也就是说,人将是超阶级,雇员将是子阶级。人们可能有年龄和体重,但没有薪水。员工是人,因此他们本来就有年龄和体重,但也因为他们是员工,所以他们将有薪水。

因此,为了方便起见,我们将首先写出超类(Person)

function Person(age,weight){
 this.age = age;
 this.weight = weight;
}

我们将使Person能够共享他们的信息

Person.prototype.getInfo = function(){
 return "I am " + this.age + " years old " +
    "and weighs " + this.weight +" kilo.";
};

接下来,我们希望有一个Person,Employee的子类

function Employee(age,weight,salary){
 this.age = age;
 this.weight = weight;
 this.salary = salary;
}
Employee.prototype = new Person();

我们将通过定义一个更适合Employee的方法来覆盖getInfo的行为

Employee.prototype.getInfo = function(){
 return "I am " + this.age + " years old " +
    "and weighs " + this.weight +" kilo " +
    "and earns " + this.salary + " dollar.";  
};

这些可以类似于原始代码使用

var person = new Person(50,90);
var employee = new Employee(43,80,50000);

console.log(person.getInfo());
console.log(employee.getInfo());

但是,在这里使用继承没有太多收获,因为Employee的构造函数与person的构造函数非常相似,并且原型中的唯一功能被覆盖。多态设计的力量在于共享行为。


2
@ user3138436-是的。原型链将被检查为首次出现getInfo,将是雇员的,因为它在链中比人的链高。那就是我说“覆盖”时的意思。
特拉维斯J

17
您无需重新使用构造函数(Person.call(this,arg))并将Employee原型设置为Person的实例。原型是共享成员所在的位置,而构造函数是创建实例特定成员的位置。您的样本使用了复制粘贴代码,再次使用了构造函数,并错误地继承了原型部分(Person的实例特定成员在Employee.prototype上没有业务,特别是如果您有可变成员)。有关使用构造函数和原型进行继承的更多信息,请访问:stackoverflow.com/a/16063711/1641941
HMR 2014年

3
对于Employee来说,重用和扩展人员的getinfo可以简单地做到,return Person.prototype.getInfo.call(this) + + "and earns " + this.salary + " dollar.";而不是复制粘贴代码。
HMR 2014年

3
多态性在这里应用?
艾伯特·杰加尼(Albert Jegani)2015年

2
@rpeg在OPs示例中可以更好地看到多态点。函数showInfo(); 接受一个通用对象。现在,多态性是根据对象类型做出不同反应的能力。我认为这个答案不够清楚,因为它在每个特定对象上调用getInfo()。
Stefan

26

正如在另一个答案中所解释的,多态性有不同的解释。

我读过的关于该主题的最好的解释是著名的类型理论家Luca Cardelli的一篇文章。该文章的名称为“了解类型,数据抽象和多态性”

它是什么?

Cardelli在本文中定义了几种类型的多态性:

  • 普遍
    • 参数
    • 包含
  • 特设
    • 上载
    • 强迫

也许在JavaScript中,很难看到多态性的影响,因为在静态类型系统中,更经典的多态性类型更加明显,而JavaScript具有动态类型系统。

因此,例如,在JavaScript编译时没有方法或函数重载或自动类型强制。用一种动态的语言,我们将其中大多数视为理所当然。由于语言的动态特性,我们都不需要JavaScript中的参数多态性。

尽管如此,JavaScript仍具有一种类型继承的形式,它以与我们在其他面向对象的编程语言(例如Java或C#)中通常采用的相似方式来模仿子类型多态性的相同思想(上面的Cardelli将其归类为包含多态性)。我在上面分享的另一个答案)。

在动态语言中非常典型的另一种多态形式称为鸭子类型

认为多态仅与面向对象的编程有关是一个错误。其他编程模型(功能,过程,逻辑等)在其类型系统中提供了不同形式的多态,可能有点不熟悉那些仅用于OOP的方式。

为什么我们需要它?

多态性在软件中培养了许多良好的属性,除了其他方面,它促进了模块化和可重用性,并使类型系统更加灵活和可扩展。没有它,就很难对类型进行推理。多态性确保只要满足公共接口,一种类型就可以被其他兼容的类型替代,因此这也促进了信息隐藏和模块化。

它是如何工作的?

这不容易回答,不同的语言有不同的实现方法。如上所述,在JavaScript的情况下,您将看到使用原型继承以类型层次结构的形式实现它,并且还可以使用鸭子类型来利用它。

这个主题有点宽泛,您在一个帖子中打开了两个很多问题。也许最好是先阅读Cardelli的论文,然后尝试理解多态性,而不管任何语言或编程范例如何,然后您将开始在理论概念与诸如JavaScript之类的任何特定语言提供的实现这些思想之间建立联系。


14

多态性的目的是什么?

多态性通过放宽类型对等的条件,使静态类型系统更加灵活,而不会损失(大量)静态类型安全性。证明程序只有在不包含任何类型错误的情况下才能运行。

多态函数或数据类型比单态函数或数据类型更通用,因为它可以在更广泛的场景中使用。从这个意义上讲,多态性代表了严格类型化语言中的泛化思想。

这如何适用于Javascript?

Javascript具有弱的动态类型系统。这种类型系统等效于仅包含一种类型的严格类型系统。我们可以将这种类型视为巨大的联合类型(伪语法):

type T =
 | Undefined
 | Null
 | Number
 | String
 | Boolean
 | Symbol
 | Object
 | Array
 | Map
 | ...

在运行时,每个值都将与这些类型替代之一相关联。而且由于Javascript的类型很弱,因此每个值都可以多次更改其类型。

如果我们从类型理论的角度考虑并认为只有一种类型,那么可以肯定地说Javascript的类型系统没有多态性的概念。相反,我们有鸭子类型和隐式类型强制。

但这不应该使我们不必考虑程序中的类型。由于Javascript中缺少类型,因此我们需要在编码过程中进行推断。我们必须为丢失的编译器辩护,即,一旦我们看了一个程序,我们不仅必须识别算法,而且还必须识别底层的(也许是多态的)类型。这些类型将帮助我们构建更可靠,更强大的程序。

为了正确执行此操作,我将向您概述多态性的最常见表现。

参数多态性(又名泛型)

参数多态性表示不同类型是可互换的,因为类型根本无关紧要。定义参数多态类型的一个或多个参数的函数必须对相应的参数一无所知,而应将它们视为相同,因为它们可以采用任何类型。这是很严格的限制,因为这样的函数只能使用不属于其数据的参数的那些属性:

// parametric polymorphic functions

const id = x => x;

id(1); // 1
id("foo"); // "foo"

const k = x => y => x;
const k_ = x => y => y;

k(1) ("foo"); // 1
k_(1) ("foo"); // "foo"

const append = x => xs => xs.concat([x]);

append(3) ([1, 2]); // [1, 2, 3]
append("c") (["a", "b"]); // ["a", "b", "c"]

临时多态性(又名重载)

临时多态性表示不同类型仅对于特定目的是等效的。在这种意义上,要等同,一种类型必须实现特定于此目的的一组功能。然后,定义一个或多个即席多态类型参数的函数需要知道哪些函数集与其每个参数相关联。

临时多态性使功能与更大的类型域兼容。下面的示例说明“映射”目的以及类型如何实现此约束。代替一组函数,“可映射”约束仅包含一个map函数:

// Option type
class Option {
  cata(pattern, option) {
    return pattern[option.constructor.name](option.x);
  }
  
  map(f, opt) {
    return this.cata({Some: x => new Some(f(x)), None: () => this}, opt);
  }
};

class Some extends Option {
  constructor(x) {
    super(x);
    this.x = x;
  }
};

class None extends Option {
  constructor() {
    super();
  }
};


// ad-hoc polymorphic function
const map = f => t => t.map(f, t);

// helper/data

const sqr = x => x * x;

const xs = [1, 2, 3];
const x = new Some(5);
const y = new None();

// application

console.log(
  map(sqr) (xs) // [1, 4, 9]
);

console.log(
  map(sqr) (x) // Some {x: 25}
);

console.log(
  map(sqr) (y) // None {}
);

亚型多态性

由于其他答案已经涵盖了亚型多态性,因此我将其跳过。

结构多态性(又名结构亚型)

结构多态性表示不同类型是等效的,如果它们以这种方式包含相同的结构,则一个类型具有另一种类型的所有属性,但可能包含其他属性。话虽这么说,结构多态性在编译时就是鸭子类型,并且无疑提供了额外的类型安全性。但是,仅因为两个值共享某些属性就声称它们是同一类型,它就完全忽略了值的语义级别:

const weight = {value: 90, foo: true};
const speed =  {value: 90, foo: false, bar: [1, 2, 3]};

不幸的是,它speed被认为是Apple的子类型,weight一旦我们比较了value属性,我们实际上就在将苹果与橙子进行比较。


1
尽管从很多方面来说,这比接受的答案更准确(并且肯定更彻底),但它没有相同的可访问性:该答案假设提问者已经太聪明了,无法打扰这个问题了:)
Jared Smith

@JaredSmith我试图将主题简化为一些易于理解的段落。但是我钻得越深,它变得越复杂。我从未在无类型语言中找到多态的好来源,因此我认为这个答案仍然很有价值。

编辑大大改善了可访问性。至于动态语言中多态的有用性,有很多,但是我很难想到JS中的一个好例子。更好的示例是Python的魔术方法,该方法允许用户定义的类型与诸如的多态函数一起使用len。也许conj来自clojure。
贾里德·史密斯

8

它是什么?

多边形=很多,态射=形式或行为转移。

我们为什么需要它?

在编程中,当我们希望函数(例如函数X)的接口足够灵活以接受不同类型或数量的参数时,使用它。另外,基于更改的参数类型或数字,我们可能希望函数X表现不同(同态)。

这个怎么运作?

我们编写了X函数的多个实现,其中每个实现都接受不同的参数类型或参数数量。根据参数的类型或数量,编译器(在运行时)决定当从某些代码中调用X时应执行X的哪种实现。

如何在javascript中实现这种多态行为?

JS不是一种类型化的语言,因此它实际上并不意味着要使用诸如多态性之类的OOP概念。但是,较新版本的JS现在包含类,并且多态性也有可能在JS中也变得有意义。其他答案提供了一些有趣的解决方法。


3

多态性是指在不同对象上调用相同方法并且每个对象以不同方式响应的能力称为POLYMORPHISM

    function Animal(sound){
    this.sound=sound;
    this.speak=function(){
    			return this.sound;
    	}
    }
//one method 
    function showInfo(obj){
    		console.log(obj.speak());
    }
//different objects
    var dog = new Animal("woof");
    var cat = new Animal("meow");
    var cow = new Animal("humbow");
//responds different ways
    showInfo(dog);
    showInfo(cat);
    showInfo(cow);


2

JavaScript是一种解释性语言,而不是一种编译语言。

编译时多态(或静态多态)编译时多态不过是Java,C ++中的方法重载

因此,方法重载在javascript中是不可能的。

但是动态(运行时)多态是运行时存在的多态,因此可以在javascript中覆盖方法

另一个例子是PHP。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.