herança baseada em protótipo vs. herança baseada em classe

208

No JavaScript, todo objeto é ao mesmo tempo uma instância e uma classe. Para fazer herança, você pode usar qualquer instância de objeto como um protótipo.

Em Python, C ++, etc. existem classes e instâncias como conceitos separados. Para fazer herança, você precisa usar a classe base para criar uma nova classe, que pode ser usada para produzir instâncias derivadas.

Por que o JavaScript foi nessa direção (orientação a objeto baseada em protótipo)? Quais são as vantagens (e desvantagens) do OO baseado em protótipo em relação ao OO tradicional baseado em classe?

Stefano Borini
fonte
10
O JavaScript foi influenciado pelo Self, que foi o primeiro idioma com herança prototípica. Naquela época, a herança clássica era toda a raiva, introduzida pela primeira vez em Simula. No entanto, a herança clássica era muito complicada. Então David Ungar e Randall Smith tiveram uma epifania depois de ler o GEB - "O evento mais específico pode servir como um exemplo geral de uma classe de eventos". Eles perceberam que as classes não são necessárias para a programação orientada a objetos. Daí o eu nasceu. Para saber como herança prototípica é melhor do que a herança clássica ler este: stackoverflow.com/a/16872315/783743 =)
Aadit M Shah
@AaditMShah O que / quem é GEB?
21716 Alex
3
@Alex GEB é um livro escrito por Douglas Hofstadter. É uma abreviação de Gödel Escher Bach. Kurt Gödel era um matemático. Escher era um artista. Bach era um pianista.
Aadit M Shah

Respostas:

201

Existem cerca de cem problemas de terminologia aqui, a maioria criada em torno de alguém (não você) tentando fazer com que a ideia deles pareça o melhor.

Todas as linguagens orientadas a objetos precisam ser capazes de lidar com vários conceitos:

  1. encapsulamento de dados junto com operações associadas nos dados, conhecidas como membros de dados e funções de membros ou dados e métodos, entre outras coisas.
  2. herança, a capacidade de dizer que esses objetos são exatamente como aquele outro conjunto de objetos, EXCETO essas mudanças
  3. polimorfismo ("muitas formas") no qual um objeto decide por si mesmo quais métodos devem ser executados, para que você possa depender do idioma para rotear suas solicitações corretamente.

Agora, na comparação:

A primeira coisa é toda a pergunta "classe" vs "protótipo". A idéia começou originalmente em Simula, onde, com um método baseado em classe, cada classe representava um conjunto de objetos que compartilhavam o mesmo espaço de estado (leia "valores possíveis") e as mesmas operações, formando assim uma classe de equivalência. Se você olhar para Smalltalk, como é possível abrir uma classe e adicionar métodos, é efetivamente o mesmo que você pode fazer em Javascript.

As linguagens OO posteriores queriam poder usar a verificação de tipo estático, então obtivemos a noção de uma classe fixa definida em tempo de compilação. Na versão de classe aberta, você tinha mais flexibilidade; na versão mais recente, você tinha a capacidade de verificar alguns tipos de correção no compilador que, de outra forma, exigiriam testes.

Em uma linguagem "baseada em classe", essa cópia ocorre no momento da compilação. Em uma linguagem de protótipo, as operações são armazenadas na estrutura de dados do protótipo, que é copiada e modificada no tempo de execução. Abstratamente, porém, uma classe ainda é a classe de equivalência de todos os objetos que compartilham o mesmo espaço de estado e métodos. Ao adicionar um método ao protótipo, você efetivamente cria um elemento de uma nova classe de equivalência.

Agora, por que isso? principalmente porque cria um mecanismo simples, lógico e elegante em tempo de execução. agora, para criar um novo objeto ou para criar uma nova classe, basta executar uma cópia profunda, copiando todos os dados e a estrutura de dados do protótipo. Você obtém herança e polimorfismo mais ou menos de graça, então: a pesquisa de método sempre consiste em solicitar a um dicionário uma implementação de método por nome.

O motivo que acabou no script Javascript / ECMA é basicamente que, quando começamos com isso há 10 anos, estávamos lidando com computadores muito menos potentes e navegadores muito menos sofisticados. Escolher o método baseado em protótipo significava que o intérprete poderia ser muito simples, preservando as propriedades desejáveis ​​da orientação a objetos.

Charlie Martin
fonte
1
Certo, esse parapente lê como se eu quisesse dizer o contrário? Dahl e Nyqvist criaram "classe" como a coleção de coisas com a mesma assinatura de método.
Charlie Martin
1
Essa mudança diz melhor?
Charlie Martin
2
Não, desculpe, o CLOS é do final dos anos 80 dreamsongs.com/CLOS.html Smalltalk de 1980 en.wikipedia.org/wiki/Smalltalk e Simula com orientação completa a objetos de 1967 a 1968 en.wikipedia.org/wiki/Simula
Charlie Martin
3
@ Stephanie, eles não são tão distintos quanto tudo isso: Python, Ruby, Smalltalk usam dicionários para pesquisa de métodos, e javascript e Self têm classes. Até certo ponto, você poderia argumentar que a diferença é apenas que as linguagens orientadas a protótipos estão expondo suas implementações. Portanto, provavelmente é bom não transformar isso em um grande negócio: provavelmente é mais parecido com o argumento entre o EMACS e o vi.
5309 Charlie Martin #
21
Resposta útil . +1 Lixo menos útil nos comentários. Quero dizer, faz diferença se o CLOS ou o Smalltalk foram os primeiros? A maioria das pessoas aqui não são historiadores de qualquer maneira.
Adam Arold
40

Uma comparação, que é levemente tendenciosa em relação à abordagem baseada em protótipos, pode ser encontrada no artigo Self: The Power of Simplicity . O artigo apresenta os seguintes argumentos a favor dos protótipos:

Criação copiando . A criação de novos objetos a partir de protótipos é realizada por uma operação simples, copiando, com uma metáfora biológica simples, a clonagem. A criação de novos objetos a partir de classes é realizada por instanciação, que inclui a interpretação das informações de formato em uma classe. A instanciação é semelhante à construção de uma casa a partir de um plano. Copiar nos atrai como uma metáfora mais simples do que a instanciação.

Exemplos de módulos preexistentes . Os protótipos são mais concretos que as classes, porque são exemplos de objetos, em vez de descrições de formato e inicialização. Esses exemplos podem ajudar os usuários a reutilizar os módulos, facilitando sua compreensão. Um sistema baseado em protótipo permite ao usuário examinar um representante típico em vez de exigir que ele faça sentido em sua descrição.

Suporte para objetos únicos . O Self fornece uma estrutura que pode facilmente incluir objetos únicos com seu próprio comportamento. Como cada objeto tem slots nomeados e os slots podem conter estado ou comportamento, qualquer objeto pode ter slots ou comportamento exclusivos. Os sistemas baseados em classe são projetados para situações em que existem muitos objetos com o mesmo comportamento. Não há suporte lingüístico para um objeto possuir seu próprio comportamento único, e é complicado criar uma classe que é garantida que tenha apenas uma instância [ pense no padrão singleton ]. O eu não sofre de nenhuma dessas desvantagens. Qualquer objeto pode ser personalizado com seu próprio comportamento. Um objeto exclusivo pode conter o comportamento exclusivo, e uma "instância" separada não é necessária.

Eliminação de meta-regressão . Nenhum objeto em um sistema baseado em classe pode ser auto-suficiente; outro objeto (sua classe) é necessário para expressar sua estrutura e comportamento. Isso leva a uma meta-regressão conceitualmente infinita: a pointé uma instância da classePoint , que é uma instância da metaclasse Point, que é uma instância da metametaclasse Point, ad infinitum. Por outro lado, em sistemas baseados em protótipos, um objeto pode incluir seu próprio comportamento; nenhum outro objeto é necessário para dar vida a ele. Os protótipos eliminam a meta-regressão.

O Self é provavelmente a primeira linguagem a implementar protótipos (também foi pioneira em outras tecnologias interessantes, como o JIT, que mais tarde entrou na JVM), portanto, a leitura dos outros papéis do Self também deve ser instrutiva.

Vijay Mathew
fonte
5
RE: Eliminação da meta-regressão: no Common Lisp Object System, que é baseado em classes, a pointé uma instância da classe Point, que é uma instância da metaclasse standard-class, que é uma instância de si mesma, ad finitum.
MaxNanasy #
Os links para os documentos do auto estão mortos. Links de trabalho: Eu: o poder da simplicidade | A Auto Bibliografia
user1201917
24

Você deve conferir um ótimo livro sobre JavaScript de Douglas Crockford . Ele fornece uma explicação muito boa de algumas das decisões de design tomadas pelos criadores de JavaScript.

Um dos aspectos importantes do design do JavaScript é o seu sistema de herança prototípica. Objetos são cidadãos de primeira classe em JavaScript, tanto que funções regulares também são implementadas como objetos (objeto 'Function' para ser mais preciso). Na minha opinião, quando foi originalmente projetado para ser executado dentro de um navegador, deveria ser usado para criar muitos objetos singleton. No DOM do navegador, você encontra essa janela, documento, etc. todos os objetos singleton. Além disso, o JavaScript é uma linguagem dinâmica de tipo fraco (ao contrário do Python, que é fortemente tipado, linguagem dinâmica); como resultado, um conceito de extensão de objeto foi implementado através do uso da propriedade 'prototype'.

Então, acho que existem alguns profissionais para o OO baseado em protótipo, conforme implementado em JavaScript:

  1. Adequado em ambientes de baixa digitação, não há necessidade de definir tipos explícitos.
  2. Torna incrivelmente fácil implementar o padrão singleton (compare JavaScript e Java a esse respeito, e você saberá do que estou falando).
  3. Fornece maneiras de aplicar um método de um objeto no contexto de um objeto diferente, adicionando e substituindo métodos dinamicamente de um objeto etc. (coisas que não são possíveis em linguagens fortemente tipadas).

Aqui estão alguns dos contras da OO prototípica:

  1. Não há maneira fácil de implementar variáveis ​​privadas. É possível implementar vars privados usando a magia de Crockford usando closures , mas definitivamente não é tão trivial quanto o uso de variáveis ​​privadas como Java ou C #.
  2. Ainda não sei como implementar várias heranças (pelo que vale a pena) em JavaScript.
Amit
fonte
2
Basta usar uma convenção de nomenclatura para vars particulares, como o Python.
A13710
1
Em js, a maneira de fazer vars privados é com fechamentos, e isso é independente do tipo de herança que você escolher.
21412 Benja
6
Crockford fez muito para danificar o JavaScript, na medida em que uma linguagem de script bastante simples se transformou em um fascínio masterbatório por seus elementos internos. O JS não possui um escopo de palavra-chave privada verdadeiro ou herança múltipla verdadeira: não tente falsificá-los.
Hal50000