Академический Документы
Профессиональный Документы
Культура Документы
Orientada a Objetos
GCC178 – Turma 14A
2017-1
Voltando ao exemplo que fizemos da Rede Social, a classe Mensagem tinha um método
exibirConteudo que foi criado para que as subclasses o sobrescrevessem.
No caso da rede social era um método protegido. Mas a situação mais comum é quando
queremos usar o polimorfismo para um método que sabemos que todas as subclasses
terão, mas que não faz sentido na superclasse.
Na verdade não é possível criar objetos de Nós dizemos nesse caso que a própria classe também é
classes que tenham métodos abstratos. abstrata.
}
...
X
public class MensagemTexto extends Mensagem {
Portanto, sempre que precisar garantir que suas subclasses tenham determinado método, declare-o como
abstrato na superclasse.
Exemplo de Métodos e Classes Abstratas
Classes abstratas são úteis não apenas quando se tem métodos abstratos.
Na verdade, com o uso de herança, muitas vezes definimos classes para as quais não
desejamos que sejam criados objetos.
Nos nossos exemplos não faria sentido criar objetos das classes FormaGeometrica e
Mensagem mesmo que elas não tivessem métodos abstratos.
Nós poderíamos então declará-las como abstratas mesmo assim, apenas para impedir
a criação de objetos delas.
Repare que uma classe pode ser declarada abstrata sem ter
métodos abstratos.
Mas o contrário não é verdadeiro. Se uma classe tiver pelo
menos um método abstrato (mesmo que por herança) ela
necessariamente precisa ser abstrata.
Resumindo Classes e Métodos Abstratos
Em resumo:
• Subclasses de superclasses abstratas só são concretas se sobrescreverem e
implementarem todos os métodos abstratos da superclasse.
• Caso contrário, elas também são abstratas.
• Portanto, métodos abstratos servem para forçar que subclasses concretas os
implementem.
Classes abstratas não podem ser instanciadas mas ainda assim definem tipos válidos.
Ou seja, posso declarar uma variável do tipo de uma classe abstrata normalmente. E
ela pode receber um objeto de uma subclasse da classe abstrata.
Sim!
Apesar de não podermos criar objetos de classes abstratas, podemos chamar seus construtores usando super nas
subclasses.
E isso é não só útil, como necessário, uma vez que a classe abstrata precisa garantir um estado inicial válido (valor
inicial de seus atributos) e, pelo princípio do encapsulamento, seus atributos precisam ser privados.
Qual seria a utilidade de uma classe abstrata que nunca é uma superclasse?
Nenhuma!
Classes abstratas se tornam úteis quando são estendidas por classes concretas.
Diagrama de Classes UML: Classes Abstratas
As formas geométricas e as figuras têm coisas em comum, apesar de eu não poder dizer
que uma forma é uma figura ou vice-versa.
Podemos pensar em uma superclasse abstrata mais geral que serve pra ambas (tem o que
é comum). E a classe controle teria uma lista de objetos dessa superclasse.
ObjetoDesenhavel teria apenas o método abstrato exibir.
Objeto
Desenhavel
Forma
Figura Simbolo
Geometrica
Retangulo Trapezio
Quadrado
Elas são no fundo classes abstratas, com todos os métodos abstratos e públicos.
Podemos dizer também que elas definem um tipo (com um nome e um
conjunto de métodos) sem nenhuma implementação para os métodos.
Interfaces em Java
Java permite que classes herdem de interfaces. Mas faz isso usando uma palavra-
chave diferente: implements.
Costumamos dizer então que uma classe implementa uma interface.
Em Java, uma classe pode estender apenas uma superclasse, mas pode implementar
várias interfaces.
Isso permite utilizar parte dos benefícios da herança múltipla, evitando alguns de seus
problemas.
Suponha que queiramos incluir animais no software de desenho e, além disso, queiramos que uma
classe Cachorro , por exemplo, herdasse de uma classe Animal já existente.
Como ObjetoDesenhavel é uma interface, nós podemos fazer o que precisamos, pois a classe
Cachorro pode estender a superclasse Animal e implementar a interface ObjetoDesenhavel.
Já se ObjetoDesenhavel fosse uma classe, isso não seria possível.
Repare que uma classe herda os métodos de uma interface como métodos abstratos. Portanto se
uma classe implementa uma interface mas não sobrescreve seus métodos ela não pode ser
concreta.
Veja também que quando temos uma hierarquia de herança, podemos ter várias classes
abstratas em uma determinada linha de hierarquia. E se nenhuma delas implementar um
método de uma interface mais ancestral, por exemplo, a subclasse concreta precisará
implementar.
Obs: o compilador te ajuda indicando quais métodos precisam ser implementados, caso
isso não seja feito.
Por exemplo:
Sabemos que uma estrutura de dados pilha precisa ter um conjunto de métodos:
• Ex: Empilhar, Desempilhar e getTamanho
Se definirmos uma interface Pilha e utilizarmos variáveis sempre do tipo Pilha. Podemos depois ter várias
implementações de Pilha...
• Ex: PilhaComoVetor, PilhaComoLista, etc.
... e não precisaremos alterar nenhum lugar onde usamos as operações de Pilha (só precisamos mudar a criação do
objeto). E mais, podemos no futuro criar qualquer novo tipo de Pilha sem alterar o código de utilização das mesmas.
Ou seja, a interface Pilha funcionou como uma especificação, um contrato a ser seguido. Quem segue a especificação
pode ser utilizado sem transtornos.
Interfaces = Especificações ou Contratos
Interface
List<E>
Class Class
ArrayList<E> LinkedList<E>
Interfaces = Especificações ou Contratos
Class Class E mais importante: pode ser que você comece utilizando uma
ArrayList<E> LinkedList<E> e depois descubra que a outra seria melhor.
Se você utilizar variáveis do tipo List você pode mudar de uma
pra outra alterando apenas a criação dos objetos.
Interface ou Classe Abstrata
Se algum método tem implementação, claro precisa ser uma classe abstrata.
Devido ao grande poder que o conceito de Interfaces (como tipo de dados) trazem
para o desenvolvimento de software, ele é amplamente utilizado.
Mas com o passar do tempo surgiu um problema.
Esse efeito colateral acaba dificultando a evolução de interfaces já desenvolvida. Imagina se o pessoal
responsável Java acrescenta um novo método em uma interface largamente utilizada; milhares de
empresas teriam que gastar ($) só para tratar isso.
Por outro lado, não poder fazer alterações em interfaces prontas impede que o código evolua
(acrescentando métodos que talvez seriam muito úteis).
Java 8 – Interfaces e Métodos default (padrão)
Para resolver esse problema a versão 8 do Java traz o conceito de métodos default
(padrão) para Interfaces.
Ou seja, agora é possível criar métodos com implementação dentro de uma interface.
E as subclasses que implementam uma interface que tenha método default compilará
mesmo que não sobrescreva o método (mas se quiser pode sobrescrevê-lo também).
void exibir();
34
Mudando de assunto, o
que acontece se eu
sobrescrever um
método estático?
Métodos Estáticos x Sobrescrita
Nós vimos que ao declarar um atributo como final, nós estamos dizendo
que ele é uma constante. Mas podemos também usar o modificador
final para métodos. O que ele faz?
Ele indica que aquele método não pode ser sobrescrito nas subclasses!
Mas pra que isso serve?
É útil para evitar que um método crítico seja sobrescrito. Exemplo: um método que se for
alterado deixa o estado do objeto inconsistente.
Além disso, métodos chamados dentro de construtores deveriam ser final, caso contrário o
objeto poderia ser criado com estado inconsistente.
Java: final para classes
Ele está certo! Você pode realmente declarar uma classe como final e dessa
forma impedir que ela se torne uma superclasse.
Exemplo: public final class MinhaClasse { ... }
É útil para objetos imutáveis. O que adiantaria projetar uma classe imutável se
alguém pudesse sobrescrever e torná-la mutável?
A classe String por exemplo é final por questões de segurança (para evitar acessos
indevidos a arquivos sem permissão).
Parte 8
Testes e depuração
39
Um ponto muito importante para o
desenvolvimento de software OO é a criação de
objetos bem comportados! ☺
Ou seja, vez ou outra se deparará com Testar se refere a verificar se uma parte
situações nas quais o sistema não do código faz o que deveria ser feito.
está funcionando como deveria. Por
isso são muito importantes as E depurar (ou debugar) se refere a
atividades de Testes e Depuração. descobrir a causa de um erro e corrigí-la.
Teste e Depuração
Um teste de unidade manual de uma classe, por exemplo, poderia ser feito da forma
apresentada abaixo.
Imagine que queremos testar uma classe chamada ClasseA.
...
ClasseATester
Imagine que o
sistema possui ClasseA ...
muitas outras
classes e nossa
ClasseA se Vamos criar uma classe auxiliar ClasseATester, que possa gerar um executável (ou
relaciona com seja, possui um método main), e cria objetos da ClasseA e faça chamadas a todos os
algumas delas. seus métodos públicos.
Repare que essa ClasseATester não fará parte do software final. Mas com ela nós
podemos fazer um pequeno programa que testa completamente a ClasseA.
Exemplo de Classe para Teste de Unidade
public class ContaTester {
public static void main(String[] args) { Esse é um trecho da classe que testa uma
Conta conta1 = new Conta(10);
classe Conta.
System.out.println("Testando numero da conta\t");
if (conta1.getNumero() == 10) { Veja que a ideia é criar objetos da classe
System.out.println("[OK]");
}
conta e testar se seu estado se mantém
else { coerente a cada chamada de método
System.out.println("[Erro] " + público.
"Esperado: 10, Retornado: " + conta1.getNumero());
}
Com essa classe pronta, se depois você
System.out.println("Testando saldo inicial da conta\t");
if (conta1.getSaldo() == 0) { precisar fazer qualquer alteração na classe
System.out.println("[OK]"); Conta, basta rodar esse programa
} novamente para ver se não causou
else {
nenhum efeito colateral.
System.out.println("[Erro] " +
"Esperado: 0; Retornado: " + conta1.getSaldo());
}
System.out.println("Testando saldo apos deposito de R$ 100,00\t");
conta1.depositar(100);
if (conta1.getSaldo() == 100) { Veja na página da disciplina a classe
System.out.println("[OK]");
completa no exemplo: Exemplo Agencia-
}
else { Contas v2.
System.out.println("[Erro] " +
"Esperado: 100; Retornado: " + conta1.getSaldo());
Testes Automatizados
Se durante o desenvolvimento ao terminar cada classe nós fizermos testes unitários antes de usá-la nos
sistema, nós diminuímos drasticamente as chances do sistema como um todo ter problema depois.
Testes automatizados na verdade são testes que partem dessa ideia mas nos quais
você implementa também o que se espera de resultado das chamadas dos
métodos. Dessa forma, eles podem ser rodados de forma automática, evitando que
você tenha que executar cada programa de teste de classe manualmente.
Muitas vezes o sistema possui muitas variáveis ou atributos envolvidos, cujos valores são alterados dentro
da lógica do sistema.
O teste de mesa nada mais é do que montar uma tabela com as chamadas de métodos, os parâmetros e
variáveis, e colocar os valores que estes vão assumindo a cada passo da execução o algoritmo.
A ideia é colocar imprimir na saída padrão informações que nos permitam acompanhar:
• A sequência de chamadas dos métodos.
• Os valores dos parâmetros.
• Os valores de variáveis locais e atributos em pontos estratégicos.
Essa estratégia é útil pois não depende de nenhum recurso especial da linguagem e
nem de alguma IDE específica. Podendo ser facilmente utilizado em qualquer situação.
Pilha de Execução
Aqui podemos verificar os valores dos estados dos
objetos, dos parâmetros e variáveis locais.
Breakpoint
Estratégias de Depuração