herança clássica vs herança prototípica em javascript

118

Pesquisei tantos links no Google e não consigo ter uma boa ideia sobre a diferença entre herança clássica e herança prototípica?

Aprendi algumas coisas com eles, mas ainda estou confuso sobre os conceitos.

Herança clássica

// 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);

A herança clássica usa herança prototípica internamente?

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

No link acima, aprendi que não podemos adicionar novos métodos em tempo de execução na herança clássica . Isso está correto? Mas você pode verificar o código acima , posso adicionar o método "mover" e quaisquer métodos em tempo de execução através do protótipo . Portanto, esta é uma herança clássica baseada em protótipo? Em caso afirmativo, o que é herança clássica real e herança de protótipo? Estou confuso com isso.

Herança 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);

Isso é semelhante à herança clássica? Estou totalmente confuso sobre o que é herança prototípica? O que é herança clássica? Por que a herança clássica é ruim?

Você pode me dar um exemplo simples para melhor entendê-los de uma maneira simples.

Obrigado,

Siva

SivaRajini
fonte
Duplicar, verifique isto: stackoverflow.com/questions/1595611/…
Silviu Burcea
5
não tenho certeza do que você está falando aqui - o primeiro bloco de código é herança prototípica, não clássica. Seu segundo bloco de código não tem herança alguma!
Alnitak
Isso pode explicar: blog.stephenwyattbush.com/2012/05/01/…
HasanAboShally
@alnitak developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… este link informa que se tratava de herança clássica. é por isso que estou confuso.
SivaRajini
Para saber mais por que você pode querer evitar a herança clássica, veja minha palestra, "Herança Clássica é Obsoleta: Como Pensar em OO Prototípico
Eric Elliott

Respostas:

248

Ambos os exemplos de código que você demonstrou em sua pergunta fazem uso da herança prototípica. Na verdade, qualquer código orientado a objetos que você escreve em JavaScript é um paradigma de herança prototípica. JavaScript simplesmente não tem herança clássica. Isso deve esclarecer um pouco as coisas:

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

Como você pode ver, a herança prototípica e a herança clássica são dois paradigmas diferentes de herança. Algumas linguagens como Self, Lua e JavaScript suportam herança prototípica. No entanto, a maioria das linguagens como C ++, Java e C # suportam herança clássica.


Uma rápida visão geral da programação orientada a objetos

Tanto a herança prototípica quanto a herança clássica são paradigmas de programação orientada a objetos (ou seja, eles lidam com objetos). Objetos são simplesmente abstrações que encapsulam as propriedades de uma entidade do mundo real (ou seja, eles representam coisas reais no programa). Isso é conhecido como abstração.

Abstração: A representação de coisas do mundo real em programas de computador.

Teoricamente, uma abstração é definida como "um conceito geral formado pela extração de características comuns de exemplos específicos". No entanto, por causa desta explicação, vamos usar a definição acima mencionada.

Agora, alguns objetos têm muitas coisas em comum. Por exemplo, uma bicicleta de lama e uma Harley Davidson têm muito em comum.

Uma bicicleta de lama:

Uma bicicleta de lama.

Uma Harley Davidson:

Uma Harley Davidson

Uma bicicleta de lama e uma Harley Davidson são duas bicicletas. Portanto, uma bicicleta é uma generalização tanto de uma bicicleta de lama quanto de uma Harley Davidson.

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

No exemplo acima, a bicicleta, a bicicleta de lama e a Harley Davidson são todas abstrações. No entanto, a bicicleta é uma abstração mais geral da bicicleta de lama e da Harley Davidson (ou seja, a bicicleta de lama e a Harley Davidson são tipos específicos de bicicletas).

Generalização: uma abstração de uma abstração mais específica.

Na programação orientada a objetos, criamos objetos (que são abstrações de entidades do mundo real) e usamos classes ou protótipos para criar generalizações desses objetos. As generalizações são criadas por meio de herança. Uma bicicleta é uma generalização de uma bicicleta de lama. Conseqüentemente, as bicicletas de lama são herdadas das bicicletas.


Programação Clássica Orientada a Objetos

Na programação clássica orientada a objetos, temos dois tipos de abstrações: classes e objetos. Um objeto, como mencionado antes, é uma abstração de uma entidade do mundo real. Por outro lado, uma classe é uma abstração de um objeto ou outra classe (ou seja, é uma generalização). Por exemplo, 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 você pode ver em linguagens de programação orientadas a objetos clássicas, os objetos são apenas abstrações (ou seja, todos os objetos têm um nível de abstração 1) e as classes são apenas generalizações (ou seja, todas as classes têm um nível de abstração maior que 1).

Objetos em linguagens de programação orientadas a objetos clássicas só podem ser criados instanciando classes:

class Human {
    // ...
}

class Man extends Human {
    // ...
}

Man johnDoe = new Man();

Em suma, nas linguagens de programação orientadas a objetos clássicas, os objetos são abstrações de entidades do mundo real e as classes são generalizações (ou seja, abstrações de objetos ou de outras classes).

Portanto, à medida que o nível de abstração aumenta, as entidades se tornam mais gerais e, à medida que o nível de abstração diminui, as entidades se tornam mais específicas. Nesse sentido, o nível de abstração é análogo a uma escala que varia de entidades mais específicas a entidades mais gerais.


Programação Prototípica Orientada a Objetos

Linguagens de programação orientadas a objetos prototípicas são muito mais simples do que as linguagens de programação orientadas a objetos clássicas porque na programação orientada a objetos prototípica temos apenas um tipo de abstração (ou seja, objetos). Por exemplo, 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 você pode ver em linguagens de programação orientadas a objetos prototípicas, os objetos são abstrações de entidades do mundo real (nesse caso, são simplesmente chamados de objetos) ou de outros objetos (nesse caso, são chamados de protótipos dos objetos que abstraem). Portanto, um protótipo é uma generalização.

Objetos em linguagens de programação orientadas a objetos prototípicas podem ser criados ex-nihilo (ou seja, do nada) ou a partir de outro objeto (que se torna o protótipo do objeto recém-criado):

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

Na minha humilde opinião, as linguagens de programação orientadas a objetos prototípicas são mais poderosas do que as linguagens de programação orientadas a objetos clássicas porque:

  1. Existe apenas um tipo de abstração.
  2. As generalizações são simplesmente objetos.

Você já deve ter percebido a diferença entre a herança clássica e a herança prototípica. A herança clássica é limitada a classes que herdam de outras classes. No entanto, a herança prototípica inclui não apenas protótipos herdados de outros protótipos, mas também objetos herdados de protótipos.


Isomorfismo de classe de protótipo

Você deve ter notado que protótipos e classes são muito semelhantes. Isso é verdade. Eles são. Na verdade, eles são tão semelhantes que você pode usar protótipos para modelar classes:

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;
    }
}

Usando a CLASSfunção acima , você pode criar protótipos que se parecem com classes:

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));

No entanto, o inverso não é verdadeiro (ou seja, você não pode usar classes para modelar protótipos). Isso ocorre porque os protótipos são objetos, mas as classes não são objetos. Eles são um tipo totalmente diferente de abstração.


Conclusão

Em suma, aprendemos que uma abstração é um "conceito geral formado pela extração de características comuns de exemplos específicos" e que a generalização é "uma abstração de uma abstração mais específica" . Também aprendemos sobre as diferenças entre herança prototípica e clássica e como ambas são duas faces da mesma moeda.

Em uma nota de despedida, gostaria de observar que existem dois padrões de herança prototípica: o padrão prototípico e o padrão construtor. O padrão prototípico é o padrão canônico de herança prototípica, enquanto o padrão do construtor é usado para fazer a herança prototípica parecer mais com a herança clássica. Pessoalmente, prefiro o padrão prototípico.

PS Sou o cara que escreveu a postagem no blog " Por que a herança prototípica é importante " e respondeu à pergunta " Benefícios da herança prototípica em relação à clássica? ". Minha resposta é a resposta aceita.

Aadit M Shah
fonte
2
obrigado pela sua resposta maravilhosa. Eu preciso entender como o padrão prototípico é melhor comparado ao padrão do construtor. Qualquer exemplo?
SivaRajini
1
Eu escrevi uma crítica comparativa sobre construtores vs protótipos em meu blog: aaditmshah.github.io/why-prototypal-inheritance-matters/…
Aadit M Shah
Então, seria correto dizer que quando usamos funções em javascript para obter herança, de alguma forma usamos o modelo de herança clássico e quando usamos objetos simples é herança prototípica (ambos internamente seguindo herança prototípica)?
Swanidhi
1
@Swanidhi Não. Se você está usando JavaScript, está usando o modelo de herança prototípico. No entanto, o JavaScript tem dois tipos de herança prototípica: usando funções (ou seja, o padrão do construtor) e usando objetos (ou seja, o padrão prototípico).
Aadit M Shah
5
@Swanidhi Não. Ainda é um protótipo. JavaScript não tem "classes" e, portanto, absolutamente nada em JavaScript clássico, incluindo construtores. Ainda é herança prototípica. Apenas uma forma estranha de herança prototípica que as pessoas confundem com a herança clássica. Em resumo, 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 isso esclareça as coisas.
Aadit M Shah
8

Antes de pular para a herança, daremos uma olhada em dois modelos principais para criar instâncias (objetos) em javascript:

Modelo clássico: o objeto é criado a partir de um projeto (classe)

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

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

Modelo prototípico: o objeto é criado diretamente a partir de outro objeto.

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

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

Em ambos os casos, a Herança * é obtida vinculando objetos usando o objeto de protótipo.

(* os métodos da classe base são acessíveis por meio da classe derivada por meio do objeto de protótipo e não precisam estar explicitamente presentes na classe derivada.)

Aqui está uma boa explicação para entender melhor ( http://www.objectplayground.com/ )

Everlasto
fonte
0

Um cachorro é um animal. Suzanna é uma cachorra. Na herança clássica, Animalé uma classe, Dogé uma subclasse de Animale suzannaé uma instância de a Dog.

Na herança prototípica, não há classe. Você tem um animal, que é um objeto. A dogé outro objeto, que clona e estende animal(o objeto protótipo). suzannaé um terceiro objeto, que copia e estende 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();

Se você escrever em Dogvez de dog, especialmente se fizer Dogalgum tipo de função de "construtor", não estará fazendo herança prototípica; você está fazendo herança (pseudo-) clássica . O fato de você estar usando Object.create()para conseguir isso não significa que está fazendo herança prototípica.

Na verdade, o JavaScript suporta apenas herança prototípica. O newoperador e o .prototypeatributo confusos existem para fazer a herança prototípica parecer uma herança (pseudo-) clássica.

Douglas Crockford explora isso detalhadamente em seu livro "JavaScript: The Good Parts".

Antonis Christofides
fonte