DEV Community 👩‍💻👨‍💻

Cover image for Orientação a objetos baseada em protótipos parte 2
Uriel dos Santos Souza
Uriel dos Santos Souza

Posted on

Orientação a objetos baseada em protótipos parte 2

Vou continuar a falar sobre orientação a objetos prototípica.
Mas este artigo não é uma continuação direta do outro artigo!
Pode ter partes repetidas!

A programação baseada em protótipos é um estilo de programação orientada a objetos em que a reutilização de comportamento (conhecida como herança) é realizada por meio de um processo de reutilização de objetos existentes que servem como protótipos. Esse modelo também pode ser conhecido como programação prototípica, orientada a protótipo, sem classes ou baseada em instância.

Em linguagens baseadas em protótipos não existem classes explícitas. Objetos herdam diretamente de outros objetos por meio de uma propriedade prototype. A propriedade prototype é chamada prototype em Self e JavaScript ou proto em Io. Existem dois métodos de construção de novos objetos: criação de objetos ("do nada") ou através da clonagem de um objeto existente. O primeiro é suportado por meio de alguma forma de literal de objeto, declarações em que os objetos podem ser definidos em tempo de execução por meio de sintaxe especial, como {...} e passados ​​diretamente para uma variável. Embora a maioria dos sistemas suporte uma variedade de clonagem, a criação de objetos do nada não é tão proeminente.

Em linguagens baseadas em classes, uma nova instância é construída por meio da função construtora de uma classe, uma função especial que reserva um bloco de memória para os membros do objeto (propriedades e métodos) e retorna uma referência a esse bloco. Um conjunto opcional de argumentos do construtor pode ser passado para a função e geralmente é mantido em propriedades. A instância resultante herdará todos os métodos e propriedades que foram definidos na classe, que atua como um tipo de modelo a partir do qual objetos de tipo semelhante podem ser construídos.

Os sistemas que suportam a criação de objetos permitem que novos objetos sejam criados do zero sem clonagem de um protótipo existente. Esses sistemas fornecem uma sintaxe especial para especificar as propriedades e comportamentos de novos objetos sem fazer referência a objetos existentes. Em muitas linguagens de protótipo existe um objeto raiz, geralmente chamado Object, que é definido como o protótipo padrão para todos os outros objetos criados em tempo de execução e que carrega métodos comumente necessários, como uma toString() função para retornar uma descrição do objeto como uma string. Um aspecto útil da criação de objetos é garantir que os nomes de slot de um novo objeto (propriedades e métodos) não tenham conflitos de namespace com o objeto de nível superior. (Na linguagem JavaScript , pode-se fazer isso usando um protótipo nulo, ou seja, Object.create(null).)

A clonagem refere-se a um processo pelo qual um novo objeto é construído copiando o comportamento de um objeto existente (seu protótipo). O novo objeto carrega então todas as qualidades do original. A partir deste ponto, o novo objeto pode ser modificado. Em alguns sistemas, o objeto filho resultante mantém um link explícito (via delegação ou semelhança ) com seu protótipo, e as alterações no protótipo fazem com que as alterações correspondentes sejam aparentes em seu clone. Outros sistemas, como a linguagem de programação Kevo, semelhante a Forth, não propagam as alterações do protótipo dessa maneira e, em vez disso, seguem um modelo mais concatenativo em que as alterações nos objetos clonados não se propagam automaticamente entre os descendentes.

Delegação
Em linguagens baseadas em protótipos que usam delegação , o tempo de execução da linguagem é capaz de despacharo método correto ou encontrar o dado certo simplesmente seguindo uma série de ponteiros de delegação (do objeto ao seu protótipo) até que uma correspondência seja encontrada. Tudo o que é necessário para estabelecer esse compartilhamento de comportamento entre objetos é o ponteiro de delegação. Ao contrário do relacionamento entre classe e instância em linguagens orientadas a objetos baseadas em classes, o relacionamento entre o protótipo e suas ramificações não requer que o objeto filho tenha uma memória ou semelhança estrutural com o protótipo além desse link. Como tal, o objeto filho pode continuar a ser modificado e alterado ao longo do tempo sem reorganizar a estrutura de seu protótipo associado como em sistemas baseados em classes. Também é importante observar que não apenas os dados, mas também os métodos podem ser adicionados ou alterados. Por esta razão,

Concatenação
Na prototipagem concatenativa - a abordagem implementada pela linguagem de programação Kevo - não há ponteiros ou links visíveis para o protótipo original a partir do qual um objeto é clonado. O objeto protótipo (pai) é copiado em vez de vinculado e não há delegação. Como resultado, as alterações no protótipo não serão refletidas nos objetos clonados. [5]

A principal diferença conceitual sob esse arranjo é que as alterações feitas em um objeto protótipo não são propagadas automaticamente para clones. Isso pode ser visto como uma vantagem ou desvantagem. (No entanto, Kevo fornece primitivas adicionais para publicar alterações em conjuntos de objetos com base em sua semelhança - as chamadas semelhanças de família ou mecanismo de família de clones [5]— em vez de por origem taxonômica, como é típico no modelo de delegação.) Às vezes também se afirma que a prototipagem baseada em delegação tem uma desvantagem adicional, pois as alterações em um objeto filho podem afetar a operação posterior do pai. No entanto, esse problema não é inerente ao modelo baseado em delegação e não existe em linguagens baseadas em delegação como JavaScript, que garantem que as alterações em um objeto filho sejam sempre registradas no próprio objeto filho e nunca nos pais (ou seja, o objeto filho valor sombreia o valor do pai em vez de alterar o valor do pai).

Em implementações simplistas, a prototipagem concatenativa terá uma pesquisa de membros mais rápida do que a prototipagem baseada em delegação (porque não há necessidade de seguir a cadeia de objetos pai), mas, inversamente, usará mais memória (porque todos os slots são copiados, em vez de haver um único slot apontando para o objeto pai). No entanto, implementações mais sofisticadas podem evitar esse problema, embora sejam necessárias compensações entre velocidade e memória. Por exemplo, sistemas com prototipagem concatenativa podem usar uma implementação copy-on-write para permitir o compartilhamento de dados nos bastidores - e essa abordagem é de fato seguida pelo Kevo. [6] Por outro lado, sistemas com prototipagem baseada em delegação podem usar o cache para acelerar a pesquisa de dados.

As linguagens OO tradicionais baseadas em classes são baseadas em uma dualidade profundamente enraizada:

As classes definem as qualidades e comportamentos básicos dos objetos.

Instâncias de objetos são manifestações particulares de uma classe. A programação baseada em protótipos usa objetos generalizados, que podem ser clonados e estendidos.

Na linguagem OOP baseada em classe, a herança e a instanciação parecem duas coisas diferentes e separadas. Quando você está definindo uma subclasse, você está criando uma nova classe que herda membros e comportamento de sua classe base e então você a estende. Depois, se você precisar manipular o estado e os dados de sua subclasse recém-criada, precisará instanciá-la. Então, e só então, você pode tratá-lo como um objeto.

No caso de POO prototípica, os atos de "herança" e "instanciação" parecem o mesmo processo. Não há noção de uma "classe", então você simplesmente pula o processo de definição de uma classe. Existem apenas objetos. Você os instancia no momento da criação.

Por exemplo, em termos de OOP baseada em classe, Fruta e maçã são classes base e filha, respectivamente.
Geralmente Fruta é uma abstração, que descreve uma "fruta" geral e não pode ser instanciada. maçã é uma classe concreta, que pode ser instanciada, pega todas as propriedades de Fruta e as estende como propriedades especiais para "maçã".

No caso de POO baseada em protótipo Fruta não é uma abstração, é um objeto concreto assim como maçã, então você pode colocá-lo na cesta (array), realizar a operação "comer" nele ou enviá-lo para um amigo (para funcionar como um argumento). Não parece realista, já que você não pode fazer isso com frutas na vida real, porque não há "Frutas" abstratas, apenas concretas.

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

Encapsulamento de dados juntamente com operações associadas nos dados, também conhecidas como membros de dados e funções de membro, ou como dados e métodos, entre outras coisas.

Herança, a capacidade de dizer que esses objetos são exatamente como esse outro conjunto de objetos, EXCETO por essas mudanças.

polimorfismo ("muitas formas") em que um objeto decide por si mesmo quais métodos devem ser executados, para que você possa depender da linguagem para rotear suas solicitações corretamente.
E isso linguagens prototipicas possuem

Por exemplo, suponha que os objetos da Vehicle class tenham um nome e a capacidade de realizar várias ações, como dirigir para o trabalho e entregar materiais de construção. "Carro do Bob" é um objeto particular (instância) da classe Vehicle.

Com o nome "carro do Bob". Em teoria, pode-se então enviar uma mensagem para "carro do Bob", mandando entregar materiais de construção.

Este exemplo mostra um dos problemas com essa abordagem: o carro do Bob, que por acaso é um carro esportivo, não é capaz de transportar e entregar materiais de construção (em qualquer sentido significativo), mas essa é uma capacidade que a classe Vehicle foi modelada para ter.

Um modelo mais útil surge do uso de subclasses para criar especializações de Vehicle; por exemplo Sports, Care, Flatbed e Truck. Somente objetos da classe Flatbed e Truck precisam fornecer um mecanismo para entregar materiais de construção;

Carros esportivos, que não são adequados para esse tipo de trabalho, precisam apenas dirigir rápido. No entanto, esse modelo mais profundo requer mais insights durante o projeto, insights que só podem vir à tona à "medida que os problemas surgem".

Essa questão é um dos fatores motivadores por trás dos protótipos. A menos que se possa prever com certeza quais as qualidades que um conjunto de objetos e classes terá em um futuro distante, não se pode projetar uma hierarquia de classes adequadamente.

Com muita frequência, o programa eventualmente precisaria de comportamentos adicionados, e seções do sistema precisariam ser redesenhadas (ou refatoradas) para separar os objetos de uma maneira diferente.

As Experiências com as primeiras linguagens OO como Smalltalk mostrou que esse tipo de problema surgiu repetidas vezes. Os sistemas tenderiam a crescer até certo ponto e depois se tornariam muito rígidos, já que as classes básicas abaixo do código do programador se tornavam simplesmente "erradas". Sem alguma maneira de alterar facilmente a classe original, sérios problemas podem surgir.

Linguagens dinâmicas como Smalltalk permitiam esse tipo de mudança por meio de métodos bem conhecidos nas classes;

Mudando a classe, os objetos baseados nela mudariam seu comportamento. No entanto, essas alterações tiveram que ser feitas com muito cuidado, pois outros objetos baseados na mesma classe podem estar esperando esse comportamento "errado": "errado" geralmente depende do contexto. (Esta é uma forma do problema da classe base frágil.)

Além disso, em linguagens como C++ , onde as subclasses podem ser compiladas separadamente das superclasses, uma mudança em uma superclasse pode realmente quebrar métodos de subclasse pré-compilados.

Em Self e outras linguagens baseadas em protótipos, a dualidade entre classes e instâncias de objetos é eliminada.

Em vez de ter uma "instância" de um objeto que é baseado em alguma "classe", em Self se faz uma cópia de um objeto existente e o altera. Então "carro do Bob", seria criado fazendo uma cópia de um objeto "Vehicle" existente e, em seguida, adicionando o método "drive fast", modelando o fato de que ele é um Porsche 911.

Objetos básicos que são usados ​​principalmente para fazer cópias são conhecidos como protótipos. Esta técnica é reivindicada para simplificar muito o dinamismo. Se um objeto existente (ou conjunto de objetos) provar ser um modelo inadequado, um programador pode simplesmente criar um objeto modificado com o comportamento correto e usá-lo em seu lugar. O código que usa os objetos existentes não é alterado.

Usando fruta como exemplo, um objeto "fruta" representaria as propriedades e a funcionalidade da fruta em geral. Um objeto "banana" seria clonado do objeto "fruta" e propriedades gerais específicas para bananas seriam anexadas.
Cada objeto "banana" individual seria clonado a partir do objeto "banana" genérico. Compare com o paradigma baseado em classes, onde uma classe "fruta" seria estendida por uma classe "banana" .

http://image.slidesharecdn.com/javascriptoops-140122020213-phpapp01/95/javascript-oops-4-638.jpg?cb=1390356199

A primeira coisa é toda a questão "classe" versus "protótipo". A ideia começou originalmente no Simula, onde com um método baseado em classes cada classe representava um conjunto de objetos que compartilhavam o mesmo espaço de estados (leia-se "valores possíveis") e as mesmas operações, formando assim uma classe de equivalência. Se você olhar para trás no Smalltalk, já que você pode abrir uma classe e adicionar métodos, isso é 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 tivemos 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 acontece em tempo de 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 em 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ê está efetivamente criando um elemento de uma nova classe de equivalência.

Agora, por que fazer 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 realizar 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: a pesquisa de métodos sempre consiste em pedir a um dicionário uma implementação de método pelo nome.

Vantagens de prototipos:
Criação por cópia. A criação de novos objetos a partir de protótipos é realizada por uma operação simples, copiando, com uma simples metáfora biológica, 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. A cópia nos atrai como uma metáfora mais simples do que a instanciação.

Exemplos de módulos pré-existentes. Os protótipos são mais concretos do que as classes porque são exemplos de objetos e não descrições de formato e inicialização. Esses exemplos podem ajudar os usuários a reutilizar módulos, tornando-os mais fáceis de entender. Um sistema baseado em protótipo permite que o usuário examine um representante típico em vez de exigir que ele dê sentido à sua descrição.

Os protótipos são flexíveis. Eles podem ser mutáveis ​​ou imutáveis.
Os objetos podem herdar de vários protótipos.
É simples. Você só tem objetos e estender objetos é a única operação necessária.

Self por exemlo 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 classes são projetados para situações em que há muitos objetos com o mesmo comportamento. Não há suporte linguístico para um objeto possuir seu próprio comportamento único, e é estranho criar uma classe que tenha a garantia de ter apenas uma instância [ pense no padrão singleton ]. Self 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 da meta-regressão. Nenhum objeto em um sistema baseado em classes pode ser autossuficiente; 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 de class Point, que é uma instância de metaclass Point, que é uma instância de metametaclass 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.

Self é provavelmente a primeira linguagem a implementar protótipos (ela também foi pioneira em outras tecnologias interessantes como JIT, que mais tarde chegou à JVM), então ler os outros artigos Self também deve ser instrutivo.

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Sobre javascript - orientação a protótipos na internet praticamente se confunde com js. Ela é a linguagem mais usada no mundo com esta forma de oop.

A razão que acabou tendo protótios em script Javascript/ECMA é basicamente lidar com computadores muito menos poderosos e navegadores muito menos sofisticados. Escolher o método baseado em protótipo significava que o interpretador poderia ser muito simples, preservando as propriedades desejáveis ​​de orientação a objetos.

A herança prototípica em JavaScript é descrita por Douglas Crockford como:

Você cria objetos protótipos e então... cria novas instâncias. Os objetos são mutáveis ​​em JavaScript, então podemos aumentar as novas instâncias, dando-lhes novos campos e métodos. Estes podem então atuar como protótipos para objetos ainda mais novos. Não precisamos de classes para fazer muitos objetos semelhantes... Objetos herdam de objetos. O que poderia ser mais orientado a objetos do que isso?

Existem dois métodos de construção de novos objetos: criação de objetos "do nada" ou através da clonagem de um objeto existente.

No caso de criação de um novo objeto tudo é simples - se você precisar de um novo objeto, basta escrever um novo objeto. Não há necessidade de descrever um objeto futuro com noção de classe, você está descrevendo um objeto e seu comportamento no momento em que o está criando. Essa é a diferença mais significativa da OOP baseada em classe.

Quando você deseja "herdar" o objeto, você o clona de um já existente. Em seguida, você pode opcionalmente transformar e estender esse objeto recém-criado para atender às suas necessidades. Depois disso, você obterá uma nova "subclasse" ou, melhor dizendo, "subobjeto" e uma nova instância dela ao mesmo tempo.

A herança prototípica tem tudo a ver com objetos. Objetos herdam propriedades de outros objetos. Isso é tudo o que há para isso. Existem duas maneiras de criar objetos usando herança prototípica em jvascript delegação e concatenação.

De agora em diante, usarei a palavra "clone" para me referir exclusivamente à herança via delegação, e a palavra "copiar" para me referir exclusivamente à herança via concatenação.

Chega de conversa. Vamos ver alguns exemplos. Digamos que eu tenha um círculo de raio 5:

var circle = {
    radius: 5
};
Enter fullscreen mode Exit fullscreen mode

Podemos calcular a área e a circunferência do círculo a partir de seu raio:

circle.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

circle.circumference = function () {
    return 2 * Math.PI * this.radius;
};
Enter fullscreen mode Exit fullscreen mode

Agora eu quero criar outro círculo de raio 10. Uma maneira de fazer isso seria:

var circle2 = {
    radius: 10,
    area: circle.area,
    circumference: circle.circumference
};
Enter fullscreen mode Exit fullscreen mode

No entanto, o JavaScript fornece uma maneira melhor de delegação. A Object.create função é usada para fazer isso:

var circle2 = Object.create(circle);
circle2.radius = 10;
Enter fullscreen mode Exit fullscreen mode

Isso é tudo. Você acabou de fazer herança prototípica em JavaScript. Não era tão simples? Você pega um objeto, clona-o, altera o que precisar, e pronto - você conseguiu um novo objeto.

Agora você pode perguntar: "Como isso é simples? Toda vez que quero criar um novo círculo, preciso clonar circlee atribuir manualmente um raio". Bem, a solução é usar uma função para fazer o trabalho pesado para você:

function createCircle(radius) {
    var newCircle = Object.create(circle);
    newCircle.radius = radius;
    return newCircle;
}

var circle2 = createCircle(10);
Enter fullscreen mode Exit fullscreen mode

Na verdade, você pode combinar tudo isso em um único literal de objeto da seguinte maneira:

var circle = {
    radius: 5,
    create: function (radius) {
        var circle = Object.create(this);
        circle.radius = radius;
        return circle;
    },
    area: function () {
        var radius = this.radius;
        return Math.PI * radius * radius;
    },
    circumference: function () {
        return 2 * Math.PI * this.radius;
    }
};


var circle2 = circle.create(10);
Enter fullscreen mode Exit fullscreen mode

Se você notar no programa acima que a função create cria um clone de circle, atribui um novo radius a ele e depois o retorna. Isso é exatamente o que um construtor faz em JavaScript:

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);
Enter fullscreen mode Exit fullscreen mode

O padrão construtor em JavaScript é o padrão prototípico invertido. Em vez de criar um objeto, você cria um construtor. A palavra chave new liga o this ponteiro dentro do construtor a um clone do prototype construtor.

Parece confuso? É porque o padrão construtor em JavaScript complica as coisas desnecessariamente. Isso é o que a maioria dos programadores acha difícil de entender.

Em vez de pensar em objetos herdando de outros objetos, eles pensam em construtores herdando de outros construtores e então ficam totalmente confusos.

Há um monte de outras razões pelas quais o padrão construtor em JavaScript deve ser evitado. Você pode ler sobre eles no meu post aqui: Construtores vs Protótipos

Então, quais são os benefícios da herança prototípica sobre a herança clássica? Vamos analisar novamente os argumentos mais comuns e explicar por quê .

  1. A herança prototípica é simples CMS afirma em sua resposta:

Na minha opinião, o maior benefício da herança prototípica é sua simplicidade.

Vamos considerar o que acabamos de fazer. Criamos um objeto circle que tinha um raio de 5. Então nós clonamos e demos ao clone um raio de 10.

Portanto, precisamos apenas de duas coisas para fazer a herança prototípica funcionar:

Uma maneira de criar um novo objeto (por exemplo, literais de objeto).
Uma maneira de estender um objeto existente (por exemplo Object.create).

Em contraste, a herança clássica é muito mais complicada. Na herança clássica você tem:

classes.
Objeto.
Interfaces.
classes Abstratas.
Construtores.
public.
protected.
private.

Você entendeu a ideia. O ponto é que a herança prototípica é mais fácil de entender, mais fácil de implementar e mais fácil de raciocinar.

Como Steve Yegge coloca em seu post clássico no blog " Portrait of a N00b ":

Metadados são qualquer tipo de descrição ou modelo de outra coisa. Os comentários em seu código são apenas uma descrição em linguagem natural da computação. O que torna metadados de metadados é que não é estritamente necessário. Se eu tenho um cachorro com alguma papelada de pedigree, e eu perco a papelada, ainda tenho um cachorro perfeitamente válido.

No mesmo sentido, as classes são apenas metadados. As classes não são estritamente necessárias para herança. No entanto, algumas pessoas (geralmente n00bs) acham as aulas mais confortáveis ​​para trabalhar. Isso lhes dá uma falsa sensação de segurança.

Bem, também sabemos que os tipos estáticos são apenas metadados. Eles são um tipo especializado de comentário direcionado a dois tipos de leitores: programadores e compiladores. Os tipos estáticos contam uma história sobre a computação, presumivelmente para ajudar os dois grupos de leitores a entender a intenção do programa. Mas os tipos estáticos podem ser descartados em tempo de execução, porque no final eles são apenas comentários estilizados. Eles são como papelada de pedigree: pode deixar um certo tipo de personalidade insegura mais feliz com seu cão, mas o cão certamente não se importa.

Como afirmei anteriormente, as aulas dão às pessoas uma falsa sensação de segurança. Por exemplo, você obtém muitos NullPointerExceptions em Java mesmo quando seu código é perfeitamente legível. Acho que a herança clássica geralmente atrapalha a programação, mas talvez seja apenas Java. Python tem um sistema de herança clássico incrível.

  1. A herança prototípica é poderosa A maioria dos programadores que vêm de uma formação clássica argumentam que a herança clássica é mais poderosa do que a herança prototípica porque tem:

Variáveis ​​privadas.
Herança múltipla.
Esta afirmação é falsa. Já sabemos que JavaScript suporta variáveis ​​privadas por meio de closures, mas e a herança múltipla? Objetos em JavaScript têm apenas um protótipo.

A verdade é que a herança prototípica suporta a herança de vários protótipos. Herança prototípica significa simplesmente um objeto herdando de outro objeto. Na verdade, existem duas maneiras de implementar a herança prototípica :

Delegação ou Herança Diferencial
Clonagem ou Herança Concatenativa
Sim JavaScript só permite que objetos deleguem a um outro objeto. No entanto, permite copiar as propriedades de um número arbitrário de objetos. Por exemplo _.extend, faz exatamente isso.

É claro que muitos programadores não consideram isso como herança verdadeira porque dizem instanceof uo isPrototypeOf contrário. No entanto, isso pode ser facilmente remediado armazenando uma matriz de protótipos em cada objeto que herda de um protótipo por meio de concatenação:

function copyOf(object, prototype) {
    var prototypes = object.prototypes;
    var prototypeOf = Object.isPrototypeOf;
    return prototypes.indexOf(prototype) >= 0 ||
        prototypes.some(prototypeOf, prototype);
}
Enter fullscreen mode Exit fullscreen mode

Portanto, a herança prototípica é tão poderosa quanto a herança clássica. Na verdade, é muito mais poderoso do que a herança clássica porque na herança prototípica você pode escolher a dedo quais propriedades copiar e quais propriedades omitir de diferentes protótipos.

Na herança clássica é impossível (ou pelo menos muito difícil) escolher quais propriedades você deseja herdar. Eles usam classes e interfaces de base virtual para resolver o problema do diamante.
Obs: O problema do diamante é algo sobre herança multipla, se tiver interesse: https://pt.stackoverflow.com/questions/233728/heran%C3%A7a-m%C3%BAltipla-e-problema-do-diamante
https://stringfixer.com/pt/Multiple_inheritance

Em JavaScript, no entanto, você provavelmente nunca ouvirá falar do problema do diamante porque pode controlar exatamente quais propriedades deseja herdar e de quais protótipos.

  1. A herança prototípica é menos redundante Este ponto é um pouco mais difícil de explicar porque a herança clássica não leva necessariamente a um código mais redundante. Na verdade, a herança, seja clássica ou prototípica, é usada para reduzir a redundância no código.

Um argumento pode ser que a maioria das linguagens de programação com herança clássica são tipadas estaticamente e exigem que o usuário declare explicitamente os tipos (ao contrário de Haskell que tem tipagem estática implícita). Portanto, isso leva a um código mais detalhado.

Java é notório por esse comportamento. Lembro-me claramente de Bob Nystrom mencionando a seguinte anedota em sua postagem no blog sobre Pratt Parsers :

Você tem que amar o nível de burocracia "por favor, assine em quadruplicado" do Java aqui.

Mais uma vez, acho que é apenas porque Java é muito ruim.

Um argumento válido é que nem todas as linguagens que possuem herança clássica suportam herança múltipla. Novamente Java vem à mente. Sim Java tem interfaces, mas isso não é suficiente. Às vezes você realmente precisa de herança múltipla.

Como a herança prototípica permite herança múltipla, o código que requer herança múltipla é menos redundante se escrito usando herança prototípica em vez de em uma linguagem que tenha herança clássica, mas não herança múltipla.

  1. A herança prototípica é dinâmica Uma das vantagens mais importantes da herança prototípica é que você pode adicionar novas propriedades aos protótipos depois que eles são criados. Isso permite que você adicione novos métodos a um protótipo que será disponibilizado automaticamente para todos os objetos que delegarem a esse protótipo.

Isso não é possível na herança clássica porque uma vez que uma classe é criada, você não pode modificá-la em tempo de execução. Esta é provavelmente a maior vantagem da herança prototípica sobre a herança clássica, e deveria estar no topo. No entanto, gosto de guardar o melhor para o final.

Conclusão
A herança prototípica importa. É importante educar os programadores JavaScript sobre por que abandonar o padrão construtor de herança prototípica em favor do padrão prototípico de herança prototípica.

Precisamos começar a ensinar JavaScript corretamente e isso significa mostrar aos novos programadores como escrever código usando o padrão prototípico em vez do padrão construtor.

Não apenas será mais fácil explicar a herança prototípica usando o padrão prototípico, mas também tornará os programadores melhores.

Tanto o padrão prototípico quanto o padrão construtor(do js) são equivalentes. Portanto, você pode se perguntar por que alguém se daria ao trabalho de usar o padrão prototípico sobre o padrão construtor. Afinal, o padrão construtor é mais sucinto que o padrão prototípico. No entanto, o padrão prototípico tem muitas vantagens sobre o padrão construtor, listado a seguir:

Os recursos funcionais podem ser usados ​​em conjunto com o create.

Como create é uma função, o programa sempre funcionará conforme o esperado.

A herança prototípica é simples e fácil de entender.

Ao usar o padrão prototípico, torna-se óbvio que um objeto herda de outro objeto.

// Example of true prototypal inheritance style 
// in JavaScript.

// object creation using the literal 
// object notation {}.
const foo = { name: "foo", one: 1, two: 2 };

// Another object.
const bar = { two: "two", three: 3 };

// Object.setPrototypeOf() is a method introduced in ECMAScript 2015.
// For the sake of simplicity, let us pretend 
// that the following line works regardless of the 
// engine used:
Object.setPrototypeOf(bar, foo); // foo is now the prototype of bar.

// If we try to access foo's properties from bar 
// from now on, we'll succeed. 
bar.one; // Resolves to 1.

// The child object's properties are also accessible.
bar.three; // Resolves to 3.

// Own properties shadow prototype properties
bar.two; // Resolves to "two"
bar.name; // unaffected, resolves to "foo"
foo.name; // Resolves to "foo"


const foo = { one: 1, two: 2 };

// bar.[[prototype]] = foo
const bar = Object.create(foo);

bar.three = 3;

bar.one; // 1
bar.two; // 2
bar.three; // 3
Enter fullscreen mode Exit fullscreen mode

Uma crítica comum feita contra linguagens baseadas em protótipos é que a comunidade de desenvolvedores de software não está familiarizada com elas, apesar da popularidade e penetração de mercado do JavaScript. Esse nível de conhecimento de sistemas baseados em protótipos parece estar aumentando com a proliferação de frameworks JavaScript e o uso complexo de JavaScript à medida que a Web amadurece.

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

A primeira linguagem prototípica foi Self do qual javascript se baseia.

Self foi uma das primeiras linguagens a usar compilação just-in-time (JIT) que posteriormente foi usado na JVM e nos navegdores web. Grande parte do desenvolvimento do Self ocorreu na Sun Os microssistemas.

Várias técnicas de compilação just-in-time foram pioneiras e aprimoradas na pesquisa do Self, pois eram necessárias para permitir que uma linguagem orientada a objetos de alto nível executasse até metade da velocidade do C otimizado.
Uma pena que Sefl não fez muito sucesso, mas acabou inspirando javascript que faz muito sucesso.

Obs: A maior parte deste artigo é tradução com algumas partes modificadas.

Fontes:
https://ericleads.wordpress.com/2013/02/11/fluent-javascript-three-different-kinds-of-prototypal-oo/

https://stackoverflow.com/questions/2800964/benefits-of-prototypal-inheritance-over-classical/16872315#16872315

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

https://developer-interview.com/p/oop-ood/what-is-prototype-based-oop-how-it-is-different-from-class-based-4

https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_baseada_em_prot%C3%B3tipos

https://stackoverflow.com/questions/816071/prototype-based-vs-class-based-inheritance

https://algodaily.com/lessons/class-vs-prototypical-inheritance

https://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=AAC2A881EBA5031D9BD77FD77009F09B?doi=10.1.1.56.4713&rep=rep1&type=pdf

https://everyday.codes/javascript/please-stop-using-classes-in-javascript/

https://www.javatpoint.com/javascript-oops-prototype-object

https://developer-interview.com/p/oop-ood/how-prototype-based-oop-is-different-from-class-based-13

https://en.wikipedia.org/wiki/Self_(programming_language)

https://en.wikipedia.org/wiki/Prototype-based_programming

https://developer-interview.com/p/oop-ood/what-are-advantages-and-disadvantages-of-prototypal-oop-12

https://developer-interview.com/p/oop-ood/what-is-prototype-based-oop-how-it-is-different-from-class-based-4

Oldest comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.