Trabalhando com Classes no JavaScript - Parte II (Herança)    

Olá pessoal!

Falando um pouco mais do mesmo assunto abordado no post anterior, vou tratar de mais um tema que aproxima o JavaScript de Orientação a Objetos. Veja bem, aproxima, mas ainda não o torna em uma linguagem Orientada a Objetos.

Hoje vou mostrar como podemos implementar Herança ou extensão de objeto no JavaScript. Vamos lá!

Tipo de Herança em JavaScript

No JavaScript padrão conseguimos estender classes, ou seja, herdar características de uma classe em uma outra classe de três formas:

  • Através do método apply
  • Através do método call
  • Através da propriedade prototype

Muitas bibliotecas JavaScript espalhadas pela internet criam novas formas de estender objetos, como no Jquery o método $.extend ou a própria forma de criar plugins. Como cada uma implementa de uma forma diferente, não irei mostrar esses tipos, apenas o nativos do JavaScript, listados acima, sendo estes compatíveis com qualquer browser com suporte a JavaScript.

Os métodos Apply e Call no JavaScript

Os métodos Apply e Call eu expliquei no meu último post. Eles são métodos internos do JavaScript, disponíveis em classes e métodos, Visite o post para entender melhor.

Como dito no último post, a função dele é alterar o contexto do this no JavaScript, mas dependendo da forma que você utilizá-lo, você poderá “ampliar” o contexto do this para o contexto da classe sobre a qual está se executando o apply ou o call, ou seja, realizar a herança. Veja o código abaixo:

   1: var w = function (s) { document.write(s); }
   2: var br = function () { w("<br/>") };
   3: var Aninal = function (nome) {
   4:     //Atributo
   5:     this.nome = null;
   6:     //Métodos
   7:     this.comer = function (comida) {
   8:         w(this.nome + " come " + comida);
   9:     }
  10:     //Código do meu construtor
  11:     this.nome = nome;
  12: }

Animal é minha classe base, e nela tento falar que qualquer animal (especificado pelo parâmetro nome) pode comer a comida passada por parâmetro do método comer(). Como eu faria para especializar essa classe, implementando a mesma função para homem? Vou mostrar utilizando o método apply como fazer isso:

   1: var Homem = function () {
   2:     //Herdo da classe Animal passando
   3:     //o nome para o construtor da classe base
   4:     Aninal.apply(this, ["Homem"]);
   5:     //Método exclusivo da classe Homem
   6:     this.falar = function () {
   7:         w("Olá");
   8:     }
   9: }
  10: var homem = new Homem();
  11: //O Resultado vai ser:
  12: //Homem come Arroz e Feijão
  13: homem.comer("Arroz e Feijão");

Veja que desta forma a herança já foi implementada. A classe Homem herda de Animal e utiliza o método comer sem alteração.

Eu poderia fazer as duas classes mais inteligentes. Digamos eu queira fazer o método Comer um pouco mais esperto, fazendo ele validar se o tipo de comido pode ser comido. Digamos que para Homem possa comer “Arroz e Feijão” e para Cachorro possa comer apenas “Ração”. Como você implementaria isso? Um switch poderia ser utilizado, mas teria que ter um case para cada tipo de animal, e se fosse descoberto um novo tipo de animal, você teria que alterar o switch. Isso é bem ruim! É para esse tipo de problema que utilizamos classes, abstrações e espacializações. Vamos implementar o problema acima e ver como fica com herança.

   1: var Aninal = function (nome) {
   2:     //Atributo
   3:     this.nome = null;
   4:     //Métodos
   5:     this.comer = function (comida) {
   6:         //O método podeComer será implementado apenas nas classes filhas
   7:         if (this.podeComer(comida)) {
   8:             w(this.nome + " come " + comida);
   9:         }
  10:         else {
  11:             w(this.nome + " NÃO come " + comida);
  12:         }
  13:     }
  14:     //Código do meu construtor
  15:     this.nome = nome;
  16: }
  17:  
  18:  
  19: var Homem = function () {
  20:     Aninal.apply(this, ["Homem"]);
  21:  
  22:     this.podeComer = function (comida) {
  23:         return comida.toLowerCase()=="arroz e feijão";
  24:     }
  25: }
  26:  
  27: var Cachorro= function () {
  28:     Aninal.apply(this, ["Cachorro"]);
  29:     
  30:     this.podeComer = function(comida){
  31:         return comida.toLowerCase()=="ração";
  32:     }
  33: }
  34:  
  35: var homem = new Homem();
  36: var cachorro= new Cachorro();
  37:  
  38: homem.comer("Arroz e Feijão");br();//pode
  39: homem.comer("pedra");br();//não pode
  40: cachorro.comer("Ração");br();//pode
  41: cachorro.comer("Arroz");br();//não pode

Execute o código e veja que a validação está funcionando, e está sendo delegada para a classe filha. A classe animal está como uma classe abstrata no C# e o método podeComer como um método abstrato.

Você pode perguntar: “Mas e se a classe filha não implementar esse método? Vai dar pau em tempo de execução. Sim! O que nós podemos fazer é uma validação para ver se o método existe, e se não existir, não executar a validação, ou mesmo jogar um exceção mais legivel. Veja:

   1: this.comer = function (comida) {
   2:     //O método podeComer será implementado apenas nas classes filhas
   3:     if (!this.podeComer || this.podeComer(comida)) {
   4:         w(this.nome + " come " + comida);
   5:     }
   6:     else {
   7:         w(this.nome + " NÃO come " + comida);
   8:     }
   9: }

Ou:

   1: this.comer = function (comida) {
   2:     //O método podeComer será implementado apenas nas classes filhas
   3:     if(!this.podeComer) throw "Defina o método podeComer.";
   4:     if (this.podeComer(comida)) {
   5:         w(this.nome + " come " + comida);
   6:     }
   7:     else {
   8:         w(this.nome + " NÃO come " + comida);
   9:     }
  10: }

A primeira solução eu acho mais interessante. No JavaScript, você pode verificar se um método existe, caso ele não exista, o retorno (underfined) é tratado como false em um IF. Tudo que eu fiz com o apply no exemplo, poderia ter feito com o call.

Implementando herança com Prototype

A propriedade prototype está presente em todos os objetos no JavaScript. Ela tem um comportamento totalmente diferente de qualquer outra. Com ela, você consegue estender classes e objetos concretos. É interessante que você consegue inclusive estender classes do próprio JavaScript. Para fazer uma analogia, ela funciona como os Extension Methods do C#.

Por exemplo, para definir uma variável do tipo Date, e definir uma data, eu posso fazer assim sempre que necessário:

   1: var data = new Date();
   2:     data.setDate(21);
   3:     data.setMonth(4);
   4:     data.setYear(2011);

Fazer isso todas as vezes não é legal, então eu posso estender a classe Date para ficar mais simples, posso fazer assim:

   1: Date.prototype.definir= function (data) {
   2:     var partes = data.split("/", 3);
   3:     this.setDate(parseFloat(partes[0]));
   4:     this.setMonth(parseFloat(partes[1]) - 1);
   5:     this.setFullYear(parseFloat(partes[2]));
   6: }
   7:  
   8: var data = new Date();
   9: data.definir("21/05/2011");

Veja que eu estou estendendo a classe Date, e toda variável do tipo Date agora terá o método definir. Se eu utilizasse o prototype diretamente em cima da variável, essa extensão teria efeito apenas na variável em questão, e não em todas as variáveis.

Essa é a utilização mais comum do prototype, mas nesse caso estou estendendo uma classe, e não herdando dela, e como eu faço isso? Vamos utilizar o mesmo exemplo dos Animais, e você verá como é feita a herança com prototype. Minha classe Animal é a mesma, vou mudar apenas a Homem e Cachorro, e ficará claro como utilizar o prototype.

   1: var Homem = function () {
   2:     this.podeComer = function (comida) {
   3:         return comida.toLowerCase() == "Arroz e Feijão";
   4:     }
   5: }
   6: //Informo de quem Homem herda
   7: Homem.prototype = new Aninal("Homem");
   8:  
   9: var Cachorro= function () {            
  10:     this.podeComer = function(comida){
  11:         return comida.toLowerCase()=="ração";
  12:     }
  13: }
  14: //Informo de quem Cachorro herda
  15: Cachorro.prototype = new Aninal("Cachorro");
  16:  
  17: //A utilização é a mesma
  18: var homem = new Homem();
  19: var cachorro= new Cachorro();
  20:  
  21: homem.comer("Arroz e Feijão");br();//pode
  22: homem.comer("pedra");br();//não pode
  23: cachorro.comer("Ração");br();//pode
  24: cachorro.comer("Arroz");br();//não pode

Veja que tirei o apply e a herança continuou a funcionar. Com o prototype eu apenas atribuo uma instância da classe que devo herdar e já está tudo funcionado. Simples e rápido.

Bom, por hoje é isso. Acredito que com isso você já seja capaz de entender e implementar herança em JavaScript. Caso tenha dúvidas, é só deixar um comentário.

Abraços e até o próximo.

21. maio 2011 20:45 by Frederico B. Emídio | Comments (0) | Permalink
Comments are closed

Sobre mim

Minha Imagem

Meu nome é Frederico Batista Emídio, trabalho com desenvolvimento de sistemas profissionalmente a oito anos, porém, já faço sites pessoais a pelo menos dez anos.

Para saber mais clique aqui.

Páginas

Calendário

<<  dezembro 2018  >>
seteququsedo
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

Visualizar posts em um Calendário
Sigua @fredemidio

MCP Asp.NET