Como a herança prototípica é praticamente diferente da herança clássica?

27

Herança, polimorfismo e encapsulamento são os três recursos mais distintos e importantes do OOP e, a partir deles, a herança tem uma estatística de alto uso nos dias de hoje. Estou aprendendo JavaScript e, aqui, todos dizem que tem uma herança prototípica, e as pessoas em todos os lugares dizem que é algo muito diferente da herança clássica.

No entanto, não consigo entender qual é a diferença entre eles do ponto de vista prático? Em outras palavras, quando você define uma classe base (protótipo) e extrai algumas subclasses dela, os dois têm acesso às funcionalidades da sua classe base e podem aumentar as funções nas classes derivadas. Se considerarmos o que eu disse ser o resultado pretendido da herança, por que deveríamos nos importar se estamos usando a versão prototípica ou clássica?

Para me esclarecer mais, não vejo diferença nos padrões de utilidade e uso da herança prototípica e clássica. Isso resulta em eu não ter interesse em saber por que eles são diferentes, pois ambos resultam na mesma coisa, OOAD. Quão praticamente (não teoricamente) a herança prototípica é diferente da herança clássica?

Saeed Neamati
fonte

Respostas:

6

Postagem recente no blog sobre JS OO

Acredito que você está comparando a emulação clássica de OO em JavaScript e OO clássico e, é claro, você não vê nenhuma diferença.

Isenção de responsabilidade: substitua todas as referências a "OO prototípico" por "OO prototípico em JavaScript". Não conheço as especificidades do Self ou qualquer outra implementação.

No entanto protótipo OO é diferente. Com protótipos, você possui apenas objetos e só pode injetar objetos em outras cadeias de protótipos de objetos. Sempre que você acessar uma propriedade em um objeto, pesquise esse objeto e quaisquer objetos na cadeia de protótipos.

Para OO prototípico, não há noção de encapsulamento. Encapsulamento é um recurso do escopo, fechamentos e funções de primeira classe, mas não tem nada a ver com OO prototípico. Também não há noção de herança, o que as pessoas chamam de "herança" é na verdade apenas polimorfismo.

No entanto, possui polimorfismo.

Por exemplo

var Dog = {
  walk: function() { console.log("walks"); }
}

var d = Object.create(Dog);
d.walk();

Claramente dtem acesso ao método Dog.walke isso exibe polimorfismo.

Então, na verdade, há uma grande diferença . Você só tem polimorfismo.

No entanto, como mencionado, se você desejar fazer isso (não tenho idéia do porquê), poderá emular o OO clássico em JavaScript e ter acesso ao encapsulamento e herança (restritos).

Raynos
fonte
1
@Rayons, Google para herança prototípica e você verá que a herança prototípica aparece. Mesmo do Yahoo!
Saeed Neamati 8/08/11
A herança @Saeed é um termo vago e comumente mal utilizado. O que eles querem dizer com "herança" eu chamo de "polimorfismo". As definições são muito vagas.
Raynos 8/08/11
No @Rayons, eu quis dizer que a palavra prototípico é o termo correto, não prototípico . Eu não falei sobre herança :)
Saeed Neamati
1
@ Saeed é um daqueles erros de digitação que minha mente apaga. Eu não pat atenção a isso
Raynos
1
Hmm .. terminologia. ick. Eu chamaria a maneira como os objetos javascript estão relacionados aos seus protótipos de "delegação". O polimorfismo parece-me uma preocupação de nível inferior, com base no fato de que um determinado nome pode apontar para um código diferente para objetos diferentes.
Sean McMillan
15

A herança clássica herda o comportamento, sem nenhum estado, da classe pai. Ele herda o comportamento no momento em que o objeto é instanciado.

A herança prototípica herda comportamento e estado do objeto pai. Ele herda o comportamento e o estado no momento em que o objeto é chamado. Quando o objeto pai é alterado no tempo de execução, o estado e o comportamento dos objetos filho são afetados.

A "vantagem" da herança prototípica é que você pode "corrigir" o estado e o comportamento depois que todos os seus objetos forem instanciados. Por exemplo, na estrutura Ext JS, é comum carregar "substituições" que corrigem os principais componentes da estrutura após a instanciação da estrutura.

Joeri Sebrechts
fonte
4
Na prática, se você está herdando um estado, está se preparando para um mundo de mágoas. O estado herdado será compartilhado se estiver em outro objeto.
Sean McMillan
1
Ocorre-me que em javascript realmente não existe um conceito de comportamento separado do estado. Além da função construtora, todo comportamento pode ser atribuído / modificado após a criação de objeto, como qualquer outro estado.
Joeri Sebrechts
Então Python não tem herança clássica? Se eu tenho class C(object): def m(self, x): return x*2e instance = C()então quando eu corro instance.m(3)eu recebo 6. Mas se eu mudar Cassim C.m = lambda s, x: x*xe eu corro instance.m(3), agora entendo 9. O mesmo acontece se eu criar um class D(C)e alterar um método, em Cqualquer instância de Dreceber o método alterado também. Estou entendendo mal ou isso significa que o Python não tem herança clássica de acordo com sua definição?
MVChr
@mVChr: ​​Você ainda está herdando o comportamento e não declarando nesse caso, o que aponta para a herança clássica, mas aponta para um problema com minha definição de "herda o comportamento no momento em que o objeto é instanciado". Não tenho muita certeza de como corrigir a definição.
Joeri Sebrechts
9

Primeiro: na maioria das vezes, você estará usando objetos, sem defini-los, e o uso de objetos é o mesmo nos dois paradigmas.

Segundo: a maioria dos ambientes prototípicos usa o mesmo tipo de divisão que os ambientes baseados em classe - dados mutáveis ​​na instância, com métodos herdados. Portanto, há muito pouca diferença novamente. (Veja a minha resposta a esta pergunta de estouro de pilha , eo Papel Auto Organizador programas sem Classes . Olhe para citeseer para uma versão PDF .)

Terceiro: a natureza dinâmica do Javascript tem uma influência muito maior que o tipo de herança. O fato de poder adicionar um novo método a todas as instâncias de um tipo, atribuindo-o ao objeto base, é puro, mas posso fazer a mesma coisa no Ruby, reabrindo a classe.

Quarto: as diferenças práticas são pequenas, enquanto a questão prática de esquecer de usar newé muito maior - ou seja, é muito mais provável que você seja afetado pela falta de um newdo que pela diferença entre o código prototípico e o código clássico .

Tudo o que foi dito, a diferença prática entre herança prototípica e clássica é que seus métodos de coisas que mantêm (classes) são os mesmos que seus dados de coisas que mantêm (instâncias). Isso significa que você pode criar suas classes aos poucos, usando as mesmas ferramentas de manipulação de objetos que você usaria em qualquer instância. (De fato, é assim que todas as bibliotecas de emulação de classe fazem isso. Para uma abordagem não muito parecida com todas as outras, veja Traits.js ). Isso é principalmente interessante se você é metaprogramador.

Sean McMillan
fonte
sim, melhor resposta IMO!
Aivar
0

A herança prototípica no JavaScript é diferente das classes das seguintes maneiras importantes:

Construtores são simplesmente funções que você pode chamar sem new:

function Circle (r, x, y) { 
  //stuff here
}
Var c = new Circle();
Circle.call(c, x, y, z); //This works and you can do it over and over again.

Não há variáveis ​​ou métodos privados, na melhor das hipóteses você pode fazer isso:

function Circle (r, x, y) {
  var color = 'red';
  function drawCircle () {
    //some code here with x, y and r
  }
  drawCircle();
  this.setX = function (x_) {
    x = x_;
    drawCircle();
  }

}
Circle.prototype.getX = function () {
   //Can't access x!
}

No exemplo anterior, você não pode estender a classe significativamente se recorrer a variáveis ​​e métodos privados falsos e, além disso, quaisquer métodos públicos que você declarou serão recriados toda vez que uma nova instância for criada.

Bjorn
fonte
Você pode estender a "classe" significativamente. Você simplesmente não pode acessar as variáveis locais color, r, x, ye drawCircleque estão vinculados ao âmbito lexical deCircle
Raynos
É verdade, você pode, mas não pode fazê-lo sem criar vários métodos 'públicos' adicionados ao contexto que são criados toda vez que você cria uma instância.
Bjorn
Esse é um padrão muito estranho que você está usando lá. Circle.call()como construtor? Parece que você está tentando descrever "objetos funcionais" (um equívoco ...)
Sean McMillan
Não é algo que eu faria, apenas estou dizendo que é possível chamar o construtor repetidamente com JavaScript, mas não em idiomas com classes tradicionais.
Bjorn
1
Mas como essas diferenças são "importantes"? Não vejo a construção de objetos chamando uma função, não usando 'novo', como 'importante', apenas uma pequena diferença de sintaxe. Da mesma forma, não ter variáveis ​​privadas não é fundamentalmente diferente, apenas uma pequena diferença. Além disso, você pode ter (algo semelhante a) variáveis ​​privadas, da maneira que descreve.
Roel 14/05