Herança e polimorfismo

Herança e polimorfismo

Doggie Daddy

Índice das lições


Umas das habilidades mais interessantes da programação orientada a objetos é a capacidade, aparentemente camaleônica, do polimorfismo e da herança como mecanismos para reutilização de código. Embora não elimine o copiar e colar, junto com a capacidade de refatoração do ambiente permitem grande reaproveitamento dos códigos e organização de uma arquitetura robusta sem um esforço exagerado.

Herança

Quando você definiu a classe Account escreveu o seguinte código no System Browser:

Object subclass: #Account
    instanceVariableNames: 'balance'
    classVariableNames: ''
    package: 'MyBank-Core'

Podemo ler o código assim: "A classe Object possui uma subclasse Account com uma variável de instância balance situada no pacote-tag MyBank-Core". Nessa configuração dizemos que a classe Account herda da classe Object.

Mas o que uma classe herda de outra? E o que significa herdar na programação orientada a objeto? E como os objetos se relacionam com a herança?

Para dar uma feição mais concreta à discussão sobre herança e polimorfismo vamos criar outra classe.

Vamos criar uma subclasse de Account com o nome SpecialAccount.

Criando uma subclasse

Para facilitar vamos repetir algumas instruções que já foram mostradas em Criando uma classe.

Localizando a classe Account

Abra o System Browser.

Busque a classe Account.

Criando a classe SpecialAccount

Vamos modificar a definição da classe Account mostrada no painel Code para criar a classe SpecialAccount.

Note que o que nos chamamos de definição de uma classe na verdade é uma mensagem que cria a classe. Portanto modificar um "definição" de uma classe e salvar é na verdade executar um código para criar uma classe ou modificá-la, a depender do argumento de subclass: já ser ou não uma classe existente.

Expanda instance side no painel Protocol para ver a hierarquia de herança de classes.

Se você clicar em Account na hierarquia expandida verá os método herdados de Account.

Se clicar no radio button Vars logo abaixo do painel Protocols vai ver a variável de instância balance herdada de Account.

Herança é o mecanismo que faz com que uma classe herde métodos e variáveis da super classe ou classe ancestral. No caso dos métodos é como se a classe que herda os tivesse definido. As variáveis de instância também são herdadas. Neste caso também é como se a subclasse tivesse definido as variáveis herdadas. No nosso caso particular a classe SpecialAccount herdou os métodos balancedeposit:initialBalanceinitialize e withdraw: da classe Account. Herdou também a variável de instância balance.

Costuma-se dizer também que SpecialAccount extende a classe Account.

Do ponto de vista das instâncias o efeito da herança de métodos é que as instâncias das subclasses podem responder às mesmas mensagens que as instâncias da superclasse sabem responder. No nosso caso particular as instâncias de SpecialAccount sabem responder às mesmas mensagens que uma instância de Account. Inclusive isto vale para o class side.

As instâncias das subclasses também possuem as mesmas variáveis instância com seus valores específicos a cada instância.

O mecanismo que permite a herança de métodos será explicado adiante.

Busca por método (Method lookup)

Para entender herança na programação orientada a objeto é fundamental entender o mecanismo de invocação de métodos acionado pelas mensagens durante a execução do programa (no run time).

Vamos descrever o que acontece com o código abaixo.

account := SpecialAccount new.
account deposit: 500.00.

Vamos nos fixar no que acontece na segunda linha account deposit: 500.00.

  1. A instância account da classe SpecialAccount recebe a mensagem deposit: com o argumento 500.00.
  2. O mecanismo de busca de métodos (method lookup) verifica se na classe da instância que recebeu a mensagem há o método deposit:. No nosso caso a classe da instância account é SpecialAccount. E a classe SpecialAccount não possui um método deposit:. Caso possuisse o método ele seria imediatamente executado.
  3. O mecanismo de busca de métodos então verifica se pode encontrar o método na superclasse. No nosso caso ele encontra o método deposit: na superclasse Account da classe SpecialAccount. E o executa imediatamente.
O mecanismo de busca de métodos (method lookup) vai subindo a hierarquia até encontrar um método que corresponda a uma mensagem e o executa. Caso não encontre ele lança um erro.

Polimorfismo

polimorfismo é a característica derivada do mecanismo de busca de método (method lookup) que permite subsitiuir uma instância de uma classe por uma instância de outra classe num trecho de programa sem que seja preciso modifcar o código para se adaptar a nova instância que estiver sendo usada.

"Se anda como um pato"

No Smalltalk dizemos que o polimorfismo é do tipo duck type. Isto é sintetizado na frase "se anda como um pato, grasna como um pato e nada como um pato então é um pato". No nosso caso envolvendo instâncias de Account e SpecialAccount, nossos patos, temos que elas respondem às mesmas mensagens (como se fosse "andar, grasnar e nadar"). Account e SpecialAccount respondem às mesmas mensagens devido ao mecanismo de herança posto em funcionamento aos colocar as classes Account e SpecialAccount numa mesma hierarquia. Mas no esquema do duck type isto não é necessário pois basta que duas instâncias respondam aos mesmos métodos mesmo sem estar numa mesma hierarquia. Não precisam nem responder a todos os métodos mas apenas àqueles que correspondem às mensagens que aparecem num determinado trecho de código. Esse tipo de polimorfismo permite grande flexibilidade no desenvolvimento em geral e particularmente no de bibliotecas de programas.

O trecho de código abaixo ilustra o conceito.

A primeira expressão account := { Account new. SpecialAccount new } atRandom. seleciona por sorteio uma instância de Account ou de SpecialAccount.

Substiuição de métodos (override)

Mas de que serve criar uma classe que herda de outra sem modificar seu comportamento, representado pelos seus métodos, ou ampliar o seu estado, representado pelas suas variáveis de instância?

Vamos corrigir isto. Queremos que nossa classe SpecialAccount permita saldo negativo até um certo limite enquanto que a classe Account só admite saldo positivos ou nulos.

Vamos reintroduzir o código em Account>>withdraw: que lança um erro se há uma tentativa de fazer uma retirada não havendo saldo suficiente.

Já uma conta que seja uma instância de SpecialAccount vai permitir saldos negativos dentro de um limite.

Ao tentar salvar o código de SpecialAccount>>withdraw: o ambiente acusa o desconhecimento da variável limit e oferece as opções de criar uma variável local (temporary variable) ou uma variável de instância (instance variable). Uma variável local (temporary) armazena um valor que só vale para esta execução do método e que é perdido ao se retornar do mesmo. Isso não nos serve pois queremos definir um limite (limit) que valha para uma determinada conta (instância), embora possa ser diferente entre as contas. Escolhemos criar uma variável de instância.

O ambiente (IDE) do Smalltalk guia você para ir criando o que for necessário em várias oportunidades. Isto permite que você não tenha o fluxo de escrita de código quebrado, por exemplo, ao ter que parar e ir criar a variável de instância lá na definição da classe e depois voltar ao método para poder salvá-lo. Com a prática cada vez mais você vai usar essa caracterísitica e ir deixando o IDE guiá-lo, numa espécie de just in time de codificação. Variáveis, métodos e classes faltantes (missing) vão sendo criados à medida que você vai codificando. Usaremos essa técnica sempre que oportuno para demonstrar o modo caraterístico de como um Smalltalker normalmente desenvolve o seu código. Não se aplica a todos os casos mas serve para a maioria deles.

Temos também agora um método withdraw: na classe SpecialAccount e a variável de instância limit também na classe SpecialAccount.

No jargão da programação orientada a objeto dizemos que o método withdraw: na classe SpecialAccount é um override (substituição) do método de mesmo nome (ou seletor) da superclasse Account. E as instâncias de SpecialAccount têm valores armazenados nas variáveis de instância balance (herdada) e limit (própria).

Se você olhar para o que aprendeu sobre o mecanismo de busca de métodos (method lookup) vai deduzir que agora as instâncias de SpecialAccount adquiriram um comportamento diferente ao responder à mensagem withdraw:. Usando a nossa metáfora do pato podemos dizer que ele grasna um pouco diferente. Mas ainda grasna. Mas antes de poder experimentar com isto precisamos estabelecer para cada instância de SpecialAccount um valor para a variável de instância limit.

Criamos abaixo o script contendo na primeira linha a mensagem newWithLimit: mesmo sabendo que não existe esse método no class side. A classe SpecialAccount não possui este método. Como já vimos ela e todas as classes repondem à mensagem new e a nossa classe SpecialAccount além da mensagem new também responde à mensagem newWithInitialBalance: que herda de Account.

Ao executar o script acima somos levados ao debugger que nos mostra uma mensagem de erro causada pela ausência de um método para responder à mensagem newWithLimit: e nos dá a oportunidade de criá-lo sem que seja necessário parar a execução.

No diálogo para selecionar a classe onde criar o método escolhemos SpecialAccount.

Selecionamos o protocolo instance creation para o nosso novo método newWithLimit:.

É apresentado um template para que o modifiquemos com a nossa implementação.

Vamos implementar de forma similar ao que fizemos na lição Criando uma classe para o método Account class>>newWithInitialBalance*.

A convenção para citar métodos no class side é escrever segundo o modelo ClassName class>>methodName.

Vamos repetir aqui o método Account class>>newWithInitialBalance para nos inspirar.

newWithInitialBalance: anAmount
    "Cria uma conta com um saldo inicial"   
            
    ^ self new
        initialBalance: anAmount

Note que o método de instância initialLimit: também não existe. No entanto o método newWithInitialBalance: compila. O fato de o método initialLimit: não existir só vai afetar a continuação da execução.

Continuamos a execução com Proceed e obtivemos um novo erro.

Repetimos o mesmo processo que usamos para criar o método newWithLimit:. Criamos o método initialLimit:, só que agora de instância, no instance side da classe SpecialAccount.

Usamos o botão Proceed para que a execução do script vá até o final. Você pode ver que a instância da classe SpecialAccount suporta uma retirada que cause um saldo negativo abaixo do valor estabelecido como limite.

Encerrando

Encerre a lição salvando a imagem.


Report Page