herencia clásica vs herencia prototípica en javascript

118

He buscado en Google tantos enlaces y no puedo tener una buena idea sobre la diferencia entre la herencia clásica y la herencia prototípica.

He aprendido algunas cosas de estos, pero todavía estoy confundido acerca de los conceptos.

Herencia clásica

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

//superclass method
Shape.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
    console.info("Shape moved.");
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); //call super constructor.
}

//subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);

¿La herencia clásica utiliza la herencia prototípica en su interior?

http://aaditmshah.github.io/why-prototypal-inheritance-matters/

Desde el enlace anterior, aprendí que no podemos agregar nuevos métodos en tiempo de ejecución en la herencia clásica . ¿Es esto correcto? Pero puede verificar el código anterior. Puedo agregar el método "mover" y cualquier método en tiempo de ejecución a través del prototipo . Entonces, ¿esto es herencia clásica basada en prototipos? Si es así, ¿cuál es la herencia clásica real y la herencia de prototipos? Estoy confundido por eso.

Herencia prototípica.

function Circle(radius) {
    this.radius = radius;
}
Circle.prototype.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};
Circle.prototype.circumference: function () {
    return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);

¿Es esto similar a la herencia clásica? Estoy totalmente confundido acerca de qué es la herencia prototípica. ¿Qué es la herencia clásica? ¿Por qué es mala la herencia clásica?

¿Puede darme un ejemplo simple para comprenderlos mejor de una manera simple?

Gracias,

Siva

SivaRajini
fuente
Duplicado, mira esto: stackoverflow.com/questions/1595611/…
Silviu Burcea
5
No estoy seguro de qué está hablando aquí: el primer bloque de código es la herencia prototípica, no clásica. ¡Su segundo bloque de código no tiene herencia en absoluto!
Alnitak
Esto puede explicarse: blog.stephenwyattbush.com/2012/05/01/…
HasanAboShally
@alnitak developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… este enlace dice que uno era herencia clásica. por eso estoy confundido.
SivaRajini
Para obtener más información sobre por qué es posible que desee evitar la herencia clásica, consulte mi charla, "La herencia clásica es obsoleta: cómo pensar en Prototypal OO" vimeo.com/69255635
Eric Elliott

Respuestas:

248

Los dos ejemplos de código que demostró en su pregunta hacen uso de herencia prototípica. De hecho, cualquier código orientado a objetos que escriba en JavaScript es un paradigma de herencia prototípica. JavaScript simplemente no tiene herencia clásica. Esto debería aclarar un poco las cosas:

                                   Inheritance
                                        |
                         +-----------------------------+
                         |                             |
                         v                             v
                    Prototypal                     Classical
                         |
         +------------------------------+
         |                              |
         v                              v
Prototypal Pattern             Constructor Pattern

Como puede ver, la herencia prototípica y la herencia clásica son dos paradigmas diferentes de herencia. Algunos lenguajes como Self, Lua y JavaScript admiten la herencia de prototipos. Sin embargo, la mayoría de lenguajes como C ++, Java y C # admiten la herencia clásica.


Una descripción general rápida de la programación orientada a objetos

Tanto la herencia prototípica como la herencia clásica son paradigmas de programación orientados a objetos (es decir, tratan con objetos). Los objetos son simplemente abstracciones que encapsulan las propiedades de una entidad del mundo real (es decir, representan palabras reales en el programa). Esto se conoce como abstracción.

Abstracción: la representación de cosas del mundo real en programas de computadora.

En teoría, una abstracción se define como "un concepto general formado al extraer características comunes de ejemplos específicos". Sin embargo, por el bien de esta explicación, usaremos la definición antes mencionada.

Ahora bien, algunos objetos tienen muchas cosas en común. Por ejemplo, una moto de barro y una Harley Davidson tienen mucho en común.

Una bici de barro:

Una moto de barro.

Una Harley Davidson:

Una Harley Davidson

Una moto de barro y una Harley Davidson son ambas motos. Por lo tanto, una bicicleta es una generalización tanto de una bicicleta de barro como de una Harley Davidson.

                   Bike
                     |
    +---------------------------------+
    |                                 |
    v                                 v
Mud Bike                       Harley Davidson

En el ejemplo anterior, la moto, la moto de barro y la Harley Davidson son todas abstracciones. Sin embargo, la moto es una abstracción más general de la moto de barro y la Harley Davidson (es decir, tanto la moto de barro como la Harley Davidson son tipos específicos de motos).

Generalización: una abstracción de una abstracción más específica.

En la programación orientada a objetos creamos objetos (que son abstracciones de entidades del mundo real) y usamos clases o prototipos para crear generalizaciones de estos objetos. Las generalizaciones se crean por herencia. Una bicicleta es una generalización de una bicicleta de barro. De ahí que las motos de barro hereden de las bicicletas.


Programación clásica orientada a objetos

En la programación clásica orientada a objetos tenemos dos tipos de abstracciones: clases y objetos. Un objeto, como se mencionó anteriormente, es una abstracción de una entidad del mundo real. Por otro lado, una clase es una abstracción de un objeto u otra clase (es decir, es una generalización). Por ejemplo, considere:

+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity |                Comments               |
+----------------------+----------------+---------------------------------------+
| 0                    | John Doe       | Real World Entity.                    |
| 1                    | johnDoe        | Variable holding object.              |
| 2                    | Man            | Class of object johnDoe.              |
| 3                    | Human          | Superclass of class Man.              |
+----------------------+----------------+---------------------------------------+

Como puede ver en los lenguajes de programación orientados a objetos clásicos, los objetos son solo abstracciones (es decir, todos los objetos tienen un nivel de abstracción de 1) y las clases son solo generalizaciones (es decir, todas las clases tienen un nivel de abstracción mayor que 1).

Los objetos en los lenguajes de programación orientados a objetos clásicos solo se pueden crear instanciando clases:

class Human {
    // ...
}

class Man extends Human {
    // ...
}

Man johnDoe = new Man();

En resumen, en los lenguajes de programación orientados a objetos clásicos, los objetos son abstracciones de entidades del mundo real y las clases son generalizaciones (es decir, abstracciones de objetos u otras clases).

Por lo tanto, a medida que aumenta el nivel de abstracción, las entidades se vuelven más generales y a medida que el nivel de abstracción disminuye, las entidades se vuelven más específicas. En este sentido, el nivel de abstracción es análogo a una escala que va desde entidades más específicas hasta entidades más generales.


Programación prototípica orientada a objetos

Los lenguajes de programación orientados a objetos prototípicos son mucho más simples que los lenguajes de programación orientados a objetos clásicos porque en la programación orientada a objetos prototípicos solo tenemos un tipo de abstracción (es decir, objetos). Por ejemplo, considere:

+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity |                Comments               |
+----------------------+----------------+---------------------------------------+
| 0                    | John Doe       | Real World Entity.                    |
| 1                    | johnDoe        | Variable holding object.              |
| 2                    | man            | Prototype of object johnDoe.          |
| 3                    | human          | Prototype of object man.              |
+----------------------+----------------+---------------------------------------+

Como puede ver en los lenguajes de programación prototípicos orientados a objetos, los objetos son abstracciones de entidades del mundo real (en cuyo caso simplemente se denominan objetos) u otros objetos (en cuyo caso se denominan prototipos de los objetos que abstraen). Por tanto, un prototipo es una generalización.

Los objetos en lenguajes de programación orientados a objetos prototípicos pueden crearse ex-nihilo (es decir, de la nada) o de otro objeto (que se convierte en el prototipo del objeto recién creado):

var human = {};
var man = Object.create(human);
var johnDoe = Object.create(man);

En mi humilde opinión, los lenguajes de programación orientados a objetos prototípicos son más poderosos que los lenguajes de programación orientados a objetos clásicos porque:

  1. Solo hay un tipo de abstracción.
  2. Las generalizaciones son simplemente objetos.

A estas alturas, debe haberse dado cuenta de la diferencia entre la herencia clásica y la herencia prototípica. La herencia clásica se limita a las clases que heredan de otras clases. Sin embargo, la herencia de prototipos incluye no solo los prototipos que heredan de otros prototipos, sino también los objetos que heredan de los prototipos.


Isomorfismo de clase prototipo

Debes haber notado que los prototipos y las clases son muy similares. Es verdad. Son. De hecho, son tan similares que puedes usar prototipos para modelar clases:

function CLASS(base, body) {
    if (arguments.length < 2) body = base, base = Object.prototype;
    var prototype = Object.create(base, {new: {value: create}});
    return body.call(prototype, base), prototype;

    function create() {
        var self = Object.create(prototype);
        return prototype.hasOwnProperty("constructor") &&
            prototype.constructor.apply(self, arguments), self;
    }
}

Con la CLASSfunción anterior , puede crear prototipos que parecen clases:

var Human = CLASS(function () {
    var milliseconds = 1
      , seconds      = 1000 * milliseconds
      , minutes      = 60 * seconds
      , hours        = 60 * minutes
      , days         = 24 * hours
      , years        = 365.2425 * days;

    this.constructor = function (name, sex, dob) {
        this.name = name;
        this.sex = sex;
        this.dob = dob;
    };

    this.age = function () {
        return Math.floor((new Date - this.dob) / years);
    };
});

var Man = CLASS(Human, function (Human) {
    this.constructor = function (name, dob) {
        Human.constructor.call(this, name, "male", dob);
        if (this.age() < 18) throw new Error(name + " is a boy, not a man!");
    };
});

var johnDoe = Man.new("John Doe", new Date(1970, 0, 1));

Sin embargo, lo contrario no es cierto (es decir, no puede usar clases para modelar prototipos). Esto se debe a que los prototipos son objetos, pero las clases no son objetos. Son un tipo de abstracción completamente diferente.


Conclusión

En resumen, aprendimos que una abstracción es un "concepto general formado al extraer características comunes de ejemplos específicos" y que la generalización es "una abstracción de una abstracción más específica" . También aprendimos sobre las diferencias entre la herencia prototípica y clásica y cómo ambas son dos caras de la misma moneda.

Como nota de despedida, me gustaría comentar que hay dos patrones de herencia prototípica: el patrón prototípico y el patrón constructor. El patrón prototípico es el patrón canónico de herencia prototípica, mientras que el patrón constructor se usa para hacer que la herencia prototípica se parezca más a la herencia clásica. Personalmente prefiero el patrón prototípico.

PD: Soy el tipo que escribió la publicación del blog " Por qué importa la herencia prototípica " y respondió a la pregunta "¿ Beneficios de la herencia prototípica sobre la clásica? ". Mi respuesta es la respuesta aceptada.

Aadit M Shah
fuente
2
gracias por tu maravillosa respuesta. Necesito entender cómo el patrón prototípico se compara mejor con el patrón constructor. ¿Algún ejemplo?
SivaRajini
1
He escrito una crítica comparativa sobre constructores vs prototipos en mi blog: aaditmshah.github.io/why-prototypal-inheritance-matters/…
Aadit M Shah
Entonces, ¿sería correcto decir que cuando usamos funciones en javascript para lograr la herencia, de alguna manera usamos el modelo de herencia clásico y cuando usamos objetos simples, la herencia prototípica (ambos internamente siguen la herencia prototípica)?
Swanidhi
1
@Swanidhi No. Si está usando JavaScript, entonces está usando el modelo de herencia prototípico. Sin embargo, JavaScript tiene dos tipos de herencia prototípica: usar funciones (es decir, el patrón del constructor) y usar objetos (es decir, el patrón prototípico).
Aadit M Shah
5
@Swanidhi No. Todavía es un prototipo. JavaScript no tiene "clases" y por lo tanto absolutamente nada en JavaScript en clásico, incluidos los constructores. Sigue siendo una herencia prototípica. Solo una forma extraña de herencia prototípica que la gente confunde con la herencia clásica. En resumen, programming with classes = classical inheritance, programming with prototypes = prototypal inheritance, programming with constructors = weird form of prototypal inheritance that looks a lot like classical inheritance. Espero que aclare las cosas.
Aadit M Shah
8

Antes de saltar a la herencia, veremos dos modelos principales para crear instancias (objetos) en javascript:

Modelo clásico: el objeto se crea a partir de un plano (clase)

class Person {
  fn() {...}
} // or constructor function say, function Person() {}

// create instance
let person = new Person();

Modelo prototípico: el objeto se crea directamente a partir de otro objeto.

// base object
let Person = { fn(){...} }

// instance
let person = Object.create(Person);

En cualquier caso, la herencia * se logra vinculando objetos utilizando un objeto prototipo.

(* Los métodos de clase base son accesibles a través de la clase derivada a través del objeto prototipo y no es necesario que estén explícitamente presentes en la clase derivada).

Aquí hay una buena explicación para comprender mejor ( http://www.objectplayground.com/ )

Everlasto
fuente
0

Un perro es un animal. Suzanna es un perro. En la herencia clásica, Animales una clase, Doges una subclase de Animaly suzannaes una instancia de a Dog.

En la herencia prototípica, no hay clase. Tienes un animal, que es un objeto. A doges otro objeto, que se clona y se extiende animal(el objeto prototipo). suzannaes un tercer objeto, que copia y extiende dog.

let animal = {hasChlorophyl: false};

let dog = Object.create(animal);
Object.assign(dog, {
  speak() {
    console.log("Woof!");
  }
});

let suzanna = Object.create(dog);
Object.assign(suzanna, {
  name: "Suzanna"
});

suzanna.speak();

Si escribe en Doglugar de dog, especialmente si crea Dogalgún tipo de función "constructora", entonces no está haciendo herencia prototípica; estás haciendo herencia (pseudo) clásica . El hecho de que esté utilizando Object.create()para lograr esto no significa que esté haciendo herencia prototípica.

De hecho, JavaScript solo admite la herencia de prototipos. El newoperador confuso y el .prototypeatributo están ahí para hacer que la herencia prototípica parezca herencia (pseudo) clásica.

Douglas Crockford explora esto en profundidad en su libro "JavaScript: The Good Parts".

Antonis Christofides
fuente