Baixe o app para aproveitar ainda mais
Prévia do material em texto
Interfaces de BD Aula 2: Orientação a objetos e modelagem comportamental Apresentação A programação estruturada fornece um meio bastante direto de expressar processos, adequando-se muito bem à implementação de algoritmos, mas com a evolução dos sistemas temos a necessidade de metodologias mais organizadas. Trabalhar com orientação a objetos exige um bom conhecimento acerca de seus pilares: Abstração, Encapsulamento, Herança e Polimor�smo. Não devemos manter uma visão baseada em processos, mas adotar uma nova perspectiva, com a modelagem de personagens em termos de ações e características físicas. Foi observado que nem tudo pode ser visto como objeto, e a modelagem comportamental desponta no mercado com base em ferramentais, como elementos genéricos e anotações. Objetivos Explicar os princípios da Orientação a Objetos; Explicar as palavras da sintaxe Java para Orientação a Objetos; Utilizar elementos de modelagem comportamental. Histórico e características gerais Um fato marcante para a tecnologia de computadores, de forma geral, foi o surgimento da arquitetura de von Neumann, que permitiu a coexistência de dados e programas em uma mesma região de memória. Essa mudança trouxe uma adaptabilidade ímpar para as máquinas, que agora poderiam executar funções básicas nas mais diversas ordens, a partir de comandos inseridos dinamicamente. Fonte: Shuttershock por catwalker Os modelos de programação passaram por diversas mudanças ao longo da história, sempre na busca de formas mais organizadas para expressar como a sequência de ações do computador deve ser orquestrada. No início havia apenas o Assembly, com a mera representação das instruções básicas do processador e uso de registradores, mas logo apareceram linguagens mais próximas da comunicação humana. As linguagens com uso de desvios foram as primeiras, muito ancoradas na metodologia do Assembly, com saltos condicionados ao valor de �ags, mas logo os próprios desvios passaram a ser criticados, e a programação estruturada, com um conjunto de estruturas para controle de �uxo, torna-se o estado da arte em termos de implementação de sistemas. Comentário Isso não durou tanto tempo assim, pois em termos de tecnologia, tudo que é regra hoje se torna inadequado no momento seguinte. Com a criação de sistemas cada vez maiores e de grande apelo visual, técnicas tradicionais de modelagem e programação estruturada começaram a entrar em colapso, e complexos trechos de código inter-relacionados, com documentação escassa e diversas replicações de processos já existentes, acabaram tornando a manutenção dos sistemas extremamente difícil, aumentando o custo e diminuindo as possibilidades evolutivas. A orientação a objetos surgiu nesse contexto, trazendo uma forma bem mais organizada de trabalho, em que a modelagem e a implementação se mostram muito mais próximas que nas técnicas ditas tradicionais. Pelo uso dessa metodologia, os programadores obtiveram uma série de vantagens no médio prazo, como facilidade de manutenção, aumento do reúso de código e documentação automatizada. Fonte: Shuttershock por Bakhtiar Zein Para podermos adotar a �loso�a orientada a objetos, devemos deixar de pensar em termos de processos e funções, pois este é o foco estruturado. Agora nosso objeto de estudo estará ancorado em personagens, pela de�nição de características físicas e ações que eles podem assumir. Por exemplo, na programação estruturada poderíamos ter um procedimento para lançar projéteis, com o ângulo e impulso iniciais, cálculo da trajetória com base na ação da gravidade e atrito com o ar, e retorno do ponto de impacto com base nesse cálculo. Em termos de orientação a objetos, nós devemos considerar a de�nição dos personagens Projétil e Canhão, e a partir disso modelar em Canhão a capacidade de atirar um Projétil. Note como essa mudança de foco nos aproxima dos aspectos observados no mundo real, determinando a primeira vantagem que foi obtida, chamada de documentação automática, pois coloca a modelagem e a implementação em um mesmo nível. Enquanto nos projetos estruturados temos diagramas voltados para processos, como Fluxogramas e DFDs, os quais apresentam grande liberdade para a fase de codi�cação, na orientação a objetos temos a UML para a diagramação, e a proximidade do nível de implementação é tão grande que ferramentas de modelagem como Enterprise Architect e Visual Paradigm conseguem efetuar engenharia reversa sobre o código já existente, de�nindo ou complementando os diagramas do projeto. A metodologia orientada a objetos é baseada em quatro pilares: 01 Abstração; 02 Encapsulamento; 03 Herança 04 Polimor�smo Pilares Clique no botão acima. Uma abstração é um modelo simpli�cado de algo, considerando apenas as características necessárias para determinado contexto. Um ser humano, por exemplo, apresenta um enorme conjunto de características, sendo capaz de efetuar as mais diversas ações, mas para �ns cadastrais, apenas atributos como identidade, CPF e nome completo podem ser su�cientes. A abstração, na orientação a objetos, é de�nida em termos de personagem, com as características físicas e as ações que pode executar. Esse tipo de modelagem cria estruturas fechadas, independentes do ambiente, o que é chamado de encapsulamento. Com os personagens de�nidos, eles podem participar de qualquer processo sem modi�cações, como se �zesse parte do roteiro de um �lme, explicando as vantagens obtidas em termos de reúso de código – nesse caso, um reúso de escopo horizontal. Além da possibilidade de reutilização, a concentração da modelagem na �gura do personagem permite que modi�cações sejam efetuadas de forma centralizada, repercutindo para todo o sistema, o que traz uma grande facilidade de manutenção. As mudanças não pararam por aí. O que foi descrito até este momento poderia ser obtido na metodologia estruturada, com uma boa organização dos processos em termos de módulos, mas surge a herança, que não poderia ser emulada no ambiente antigo. Com a herança é possível de�nir um novo tipo de personagem a partir de outro já existente, aproveitando as características do antigo dentro de um processo incremental, o que viabilizou o reúso e a manutenção no escopo vertical. Claro que esse reaproveitamento de funcionalidades antigas pode levar à necessidade de adaptações a novos contextos, em que o quarto pilar da orientação a objetos, o polimor�smo, permitirá a adaptação com a modi�cação de verbos herdados. Mesmo uma metodologia bem organizada pode ser utilizada da forma errada, e os programas orientados a objetos acabam trazendo muitos vícios de metodologias antigas. Diante disso, modelagens padronizadas para a solução de problemas recorrentes passam a ser adotadas, por meio de ferramentas conhecidas como padrões de desenvolvimento. Aliados aos padrões, elementos já previstos na orientação a objetos, como classes template, despontam no mercado na forma de elementos genéricos. O mercado começa a abraçar a modelagem comportamental, que inclui ainda as anotações e injeção de código. Abstração A Programação Orientada a Objetos (POO) teve uma utilização mais ampla com a popularização das interfaces grá�cas, pois a metodologia estruturada não era a melhor opção para construir ambientes de janelas. Nas primeiras versões dessas interfaces era necessário utilizar chamadas para as APIs do sistema operacional na construção de cada janela, de forma procedimental, mas com a POO podemos de�nir um personagem denominado Janela, o qual terá atributos como posição, largura e altura, e será capaz de efetuar ações como abrir, fechar, minimizar e maximizar, replicando o personagem quantas vezes forem necessárias para implementar as interfaces dos sistemas. O que estamos fazendo aqui é o processo conhecido como abstração, que se refere à de�nição de um modelo simpli�cado de algo. Quando abstraímos um elemento, estamos preocupados apenas com os detalhes que sejam relevantes para o problema de interesse para o sistema, e essas abstrações serão representadas comoclasses na POO, as quais trazem a de�nição dos atributos e métodos suportados pelos personagens. Os atributos de�nem características físicas, como cor, idade, endereço, ou sexo, enquanto os métodos são ações, ou verbos, que são praticadas, como comer, andar ou dormir. Comentário Neste ponto podemos abandonar a palavra personagem e passar a tratar dos dois níveis de programação que utilizaremos: Classes e Objetos. Classe Uma classe funciona como um molde (tipo ou domínio), de forma a de�nir como serão os objetos criados a partir dela. Ela de�ne apenas os tipos dos atributos, sem assumir valores. Objetos Os objetos, por sua vez, são como variáveis, ou elementos físicos, criados a partir do molde que é a classe. Cada objeto irá assumir valores especí�cos para os atributos de�nidos na classe. É justamente a partir disso que podemos justi�car uma expressão muito comum na literatura acerca de orientação a objetos: "Um objeto é a instância de uma classe". Começaremos com um exemplo simples, de�nindo uma classe Pessoa, com os atributos necessários para um processo cadastral e um método para exibição de dados. A palavra class é utilizada pelo Java para de�nir uma estrutura de classe. public class Pessoa { String nome; String telefone; void exibir(){ System.out.println(nome+"::"+telefone); } } Após a de�nição da classe, podemos criar um programa de teste com algumas instâncias (objetos), assumindo valores próprios, e exibindo seus dados na chamada ao método de exibição. public class TestePessoa { public static void main(String[] args) { Pessoa p1 = new Pessoa(); Pessoa p2 = new Pessoa(); p1.nome = "João"; p1.telefone = "1111-1111"; p2.nome = "Maria"; p2.telefone = "2222-2222"; p1.exibir(); p2.exibir(); } } Observe a presença do operador new na de�nição das instâncias. Esse é um detalhe fundamental no estudo da orientação a objetos, pois o operador é responsável pela alocação de memória, o que nos permite dizer que todo objeto pode ser interpretado como um ponteiro. Na sequência, teremos o preenchimento dos atributos nome e telefone para cada um deles, além de chamadas ao método exibir, obtendo a saída visualizada a seguir. Ao de�nir uma classe, podemos acrescentar métodos voltados para o preenchimento dos valores iniciais dos atributos, os quais são chamados de construtores. Eles não alocam os objetos na memória, o que é feito pelo operador new, mas permitem inicializar os atributos do objeto em seu primeiro momento de existência. Em algumas linguagens temos também o método destrutor, que é usado na desalocação do objeto, mas o Java não necessita dele, pois conta com uma tecnologia denominada garbage collector, ou coletor de lixo. Com o uso do coletor de lixo, qualquer objeto que não possa ser acessado e não tenha mais utilidade para o programa será removido da memória. public class Pessoa { String nome; String telefone; Pessoa(String nome, String telefone){ this.nome = nome; this.telefone = telefone; } Pessoa(){ this("",""); } void exibir(){ System.out.println(nome+"::"+telefone); System.out.println(nome+"::"+telefone); } } Modi�camos a classe Pessoa, acrescentando dois métodos construtores. Na linguagem Java, e outras derivadas do C++, um método construtor deve ser criado com o uso do nome da classe, e pode apresentar um conjunto de parâmetros que serão utilizados na inicialização do objeto. O uso de métodos com o mesmo nome, mas com listas de parâmetros diferentes, trata de uma técnica existente já na programação estruturada, a qual recebeu o nome de sobrecarga, como pudemos observar na classe Pessoa. Construtores sobrecarregados são comuns no Java, permitindo que os objetos sejam inicializados da melhor forma para cada contexto previsto. Além do uso de sobrecarga nos métodos construtores, observe a presença do operador this, também conhecido como ponteiro de autorreferência, que permite diferenciar os parâmetros do método e os atributos de mesmo nome no objeto. Quando de�nimos os parâmetros do construtor, podemos utilizar qualquer nome, mas se tivéssemos adotado uma nomenclatura que não fosse equivalente aos atributos, teríamos uma perda de semântica. A forma mais fácil de ler a primeira linha com this seria: “O nome deste objeto receberá o nome passado como parâmetro”. Podemos alterar nossa classe de teste para utilizar o construtor de Pessoa com base em parâmetros, simpli�cando a escrita, mas sem alterar a saída. Note que a escolha da versão sobrecarregada que será executada ocorre a partir da análise dos tipos de dados envolvidos na chamada. public class TestePessoa { public static void main(String[] args) { Pessoa p1 = new Pessoa("João","1111-1111"); Pessoa p2 = new Pessoa("Maria","2222-2222"); p1.exibir(); p2.exibir(); } } Não podemos deixar de observar outra utilização do operador this, com a presença de parênteses e a passagem de dois textos vazios. Nesse ponto do código, o construtor padrão chama o construtor parametrizado, passando valores vazios (não nulos) nos parâmetros nome e telefone. Herança O segundo pilar da orientação a objetos é a herança, voltada para o reúso vertical, com a de�nição de novas classes a partir de outras já existentes, reaproveitando os atributos e métodos originais. Apesar de a técnica oferecer um grande nível de reutilização, ela também aumenta o acoplamento, pois as mudanças de uma classe impactam todos os seus descendentes, algo que pode facilitar a manutenção, mas também pode ter efeitos negativos. Um dos primeiros contatos que temos com o conceito de herança ocorre nos estudos primários de biologia, quando somos apresentados a uma hierarquia de classes nas quais enquadramos os diversos tipos de animais. Para de�nir um mamífero, não partimos de um modelo vazio, mas aproveitamos todas as características da classe de animais de sangue quente, a qual foi de�nida a partir da classe de vertebrados. Fonte: Shuttershock por Vecton Na sintaxe do Java, a descendência da classe é de�nida por meio da palavra reservada extends, e assim como podemos acessar os atributos e métodos internos de um objeto com this, utilizamos a palavra super para ter acesso aos elementos herdados da classe original. public class Profissional extends Pessoa{ String profissao; Profissional(String nome, String telefone, String profissao) { super(nome, telefone); this.profissao = profissao; } } A nova classe, com o nome Pro�ssional, descendente de Pessoa com o uso de extends, acumulando os métodos e atributos originais, e de�nindo um novo atributo com o nome pro�ssao. Note que de�nimos um novo construtor, com três parâmetros, em que os dois primeiros são repassados ao construtor herdado de Pessoa, pelo uso de super, e o terceiro é associado ao atributo interno por meio do operador this, da mesma forma que foi feito na classe Pessoa. Atenção Na linguagem Java, a de�nição de um construtor, no nível da classe, oculta todos os construtores herdados. Encapsulamento Classes são estruturas fechadas e devem ser capazes de determinar quais atributos e métodos poderão ser disponibilizados para o mundo exterior, da mesma forma que um elemento físico, como os carros, nos quais temos acesso ao volante e pedais, mas não à câmara de combustão do motor. Nas classes, o nível de acesso de�ne a visibilidade do atributo ou método. Podemos contar com três níveis padronizados de acesso: Público (public) Permite que qualquer um acesse o atributo ou método; Privado (private) Não permite acessos externos, sendo utilizado apenas na programação interna da classe; Protegido (protected) Permite a utilização na classe e nos descendentes, mas não permite acessos externos. Além dos níveis clássicos de acesso, cada linguagem pode apresentar níveis próprios, como o published, do Delphi, que deixa o atributo visível no editor visual de interfaces grá�cas. Para o Java, se não de�nimos nada em termos de nível de acesso, o padrão é o de pacote, ou seja, todas as classes do mesmo pacote podem acessar, mas se ocorrer a importaçãodesse pacote por uma classe pertencente a outro, a classe externa não terá acesso aos atributos e métodos com nível de pacote. Saiba mais As bibliotecas de classes do Java são organizadas em pacotes, basicamente estruturas de diretórios nos quais salvamos os arquivos das classes. Com o pacote de�nido, as classes constituintes podem ser utilizadas em outros arquivos de código-fonte com o uso de import. É bastante comum a necessidade de controlar o acesso a um determinado atributo sem, no entanto, impedir esse acesso. Nesse ponto teremos os getters e os setters. Esse controle é chamado de encapsulamento, que trata de mais um dos pilares da orientação a objetos e, em termos formais, visa a ocultar detalhes da implementação interna da classe, fornecendo apenas uma interface com métodos de negócio e métodos de acesso. O método getter obtém o valor de um atributo interno, enquanto no setter escrevemos um valor no atributo, momento em que pode ser feita uma validação de restrições associadas ao valor. O conjunto formado por esses métodos de�ne uma propriedade da classe. public class Pessoa { public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getTelefone() { return telefone; } public void setTelefone(String telefone) { this.telefone = telefone; } private String nome; private String telefone; public Pessoa(String nome, String telefone){ this.nome = nome; this.telefone = telefone; } public Pessoa(){ this("",""); } public void exibir(){ System.out.println(getNome()+"::"+getTelefone()); } } Agora temos, na classe Pessoa, a de�nição dos níveis de acesso e a presença dos métodos de leitura e de escrita, e para não ocorrer perda de semântica devido a alteração de nomes, o uso de this é necessário nos setters. Essa alteração causaria impacto na primeira versão da classe de testes, que apresentaria erro devido ao uso de private nos atributos, forçando a modi�cação do código para a adoção dos métodos de escrita. public class TestePessoa { public static void main(String[] args) { Pessoa p1 = new Pessoa(); p1.setNome("João"); p1.setTelefone("1111-1111"); p1.exibir(); } } Devemos lembrar que estamos trabalhando em um ambiente integrado em busca de produtividade, e como tal, não modi�caremos manualmente todo o código. Clique com o botão direito sobre qualquer um dos atributos, no editor de código- fonte, e escolha a opção Refactor..Encapsulate Fields, ou Refatorar..Encapsular Campos, na versão em português, selecionando as opções necessárias para a geração dos getters e setters, e tanto a classe como todos os trechos de código- fonte nos quais os atributos são usados serão alterados de forma automática, após o clique em Refactor (Refatorar). Polimor�smo Quando herdamos os métodos de uma classe, alguns deles podem não se enquadrar nas necessidades do descendente, levando à necessidade de um ferramental que traga uma característica adaptativa na implementação de programas orientados a objetos. Nesse ponto devemos analisar o último dos pilares da orientação a objetos, o polimor�smo, que pode ser traduzido como múltiplas formas. Na prática, o polimor�smo se traduz na capacidade de modi�car um método herdado para que se adapte a um novo contexto, permitindo aproveitar ou não as funcionalidades originais. Essa pode ser considerada a característica mais poderosa da orientação a objetos, proporcionando grande �exibilidade a qualquer sistema, e devemos compreendê-la corretamente para tirar o melhor proveito da metodologia. Considere, por exemplo, a classe Pro�ssional, em que o método trabalhar representa o conjunto de ações especializadas que devem ser executadas pelas suas instâncias. É bem simples compreender que um médico trabalha de forma diferente de um advogado, ou um engenheiro, mas todos eles são pro�ssionais, e o verbo não muda, ou seja, todos trabalham. Cada classe deverá sobrescrever o método trabalhar para que se adeque a suas necessidades. Note que aqui falamos de sobrescrita, em que ocorre a reprogramação do método herdado, sem a mudança da assinatura, e não pode ser confundido com sobrecarga, em que temos o mesmo método com várias assinaturas, sem a necessidade da utilização de herança. Voltando à classe Pro�ssional de nosso exemplo de herança, se testou os objetos instanciados, já percebeu que o método exibir continua mostrando apenas o nome e o telefone de�nidos em Pessoa. Para que ela funcione corretamente, devemos modi�car o método herdado. public class Profissional extends Pessoa{ private String profissao; public Profissional(String nome, String telefone,String profissao) { super(nome, telefone); this.profissao = profissao; } @Override public void exibir() { super.exibir(); System.out.println("\tTrabalha como "+profissao); } } Note que o novo método exibir chama o método originalmente herdado de Pessoa pelo uso de super. Essa chamada não é obrigatória, mas é muito comum, pois além de aproveitar o que já existia e apenas completar a funcionalidade requerida, garante a continuidade da manutenção, já que as alterações efetuadas no método exibir da classe Pessoa irão repercutir diretamente no exibir de Pro�ssional, assim como de todos os descendentes que solicitam a execução do ancestral. Observe também a presença da anotação @Override, que é colocada nos métodos polimór�cos e signi�ca sobrescrita, não devendo ser confundida, como citado anteriormente, com a sobrecarga, cujo nome em inglês seria overload. Se perguntamos qual o animal de estimação de alguém, a pessoa pode responder que é um cachorro, um gato, ou outro tipo de animal. Isso se deve ao fato de que um cachorro é um animal, assim como os demais, e na orientação a objetos podemos aplicar a mesma regra. Atenção Na programação orientada a objetos, qualquer descendente pode ser utilizado no lugar da classe original. Essa regra permite outra interpretação do polimor�smo, em que dizemos que um objeto pode assumir múltiplas formas, e os métodos se comportam de acordo com a forma (descendência) utilizada. public class TestePessoaProfissional { public static void main(String[] args) { Pessoa[] pessoas = { new Pessoa("Joao","1111-1111"), new Pessoa("Maria","2222-2222"), new Profissional("Luiz","3333-3333","Advogado")}; for(int i=0; i<3; i++) pessoas[i].exibir(); } } No código podemos observar um vetor de objetos do tipo Pessoa, com duas instâncias de Pessoa e uma de Pro�ssional, o que é possível devido à regra de que o descendente pode ser utilizado no lugar do original. Em seguida, temos um loop em que é chamado o método exibir de cada objeto do vetor, gerando a saída que pode ser observada a seguir. Observe atentamente a impressão dos dados do terceiro objeto, pois apesar de ser um vetor do tipo Pessoa, foi executada a versão de Pro�ssional, e isso se deve ao polimor�smo, já que o objeto sabe que é um pro�ssional e executa a versão correta, de�nida em sua classe. Comentário No Java podemos utilizar o operador instanceof para testar se um objeto é de uma determinada classe ou descendente dela. Com esse exemplo, conseguimos compreender o signi�cado de polimor�smo na prática, ou seja, múltiplas formas de implementar o mesmo verbo. Elementos abstratos A palavra abstração tem origem no verbo abstrair, ou seja, analisar um ou mais aspectos contidos em um todo (de�nição do dicionário). Isso é o que fazemos ao de�nir nossas classes, com o conjunto de atributos e métodos necessários ao domínio do sistema. Normalmente confundimos muito as palavras abstração e abstrato, mas elas apresentam signi�cados muito distintos. Enquanto uma abstração é um modelo simpli�cado de algo, um elemento abstrato é algo intangível, que não é palpável ou concreto. Um exemplo clássico de algo abstrato seria mamífero, de�nindo o conjunto de animais que mamam, pois não existe um mamífero genérico, sendo uma necessidade para os descendentes, como cachorro ou gato, implementar o verbo mamar para que sejam considerados mamíferos.Ainda podem existir níveis intermediários abstratos, que de�nem regras adicionais, mas ainda não representam um elemento concreto, como felino ou equino. Esse conceito é implementado na orientação a objetos pela de�nição de classes abstratas, trazendo estruturas incompletas, que não podem gerar objetos, mas que servem de base para impor alguma característica funcional aos descendentes, já que eles serão obrigados a implementar as funcionalidades abstratas que foram previstas no ancestral. Podemos ver que, mesmo não podendo gerar objetos, o elemento abstrato de�ne uma regra para seus descendentes. public abstract class Mamifero { protected String familia; public abstract void mamar(); public String getFamilia(){ return familia; } } Um método abstrato é de�nido com o uso da palavra abstract, da mesma forma que a classe abstrata. Para que uma classe seja abstrata, basta que um método seja abstrato, e para que a classe seja concreta ela não pode conter nenhum método abstrato. Para que os descendentes de Mamifero se tornem classes concretas, eles são obrigados a implementar o método mamar. Novamente temos métodos polimór�cos, mas o uso de super não seria viável, já que é a sobrescrita de um elemento abstrato. public class Cachorro extends Mamifero{ public Cachorro() { familia = "Canidae"; } @Override public void mamar() { System.out.println("Cachorro mamando..."); } } public class Gato extends Mamifero{ public Gato() { familia = "Felidae"; } @Override public void mamar() { System.out.println("Gato mamando..."); } } public class ExemploAbstratos { public static void main(String[] args) { Mamifero m = new Gato(); m.mamar(); } } Na classe de teste é instanciado um Gato no lugar de Mamifero, sendo chamado em seguida o método mamar, e devido ao polimor�smo será executada a versão de�nida no descendente, causando a impressão da frase “Gato mamando...”. Outro tipo de elemento abstrato é a interface, tratando de um conjunto de assinaturas de métodos que devem ser implementados pelas classes. Aqui estamos interessados no artefato de programação utilizado pela orientação a objetos, não devendo ser confundida com interface visual, que é voltada para a interação com o usuário. Em termos práticos, uma interface é um conjunto de assinaturas para métodos abstratos, e devido à sua natureza, os métodos são considerados por padrão públicos e abstratos, sem o uso de palavras reservadas. Uma observação é a de que as interfaces não aceitam atributos, sendo de�nidas com o uso da palavra interface no lugar de class. public interface CriptoData { String decripto(); void cripto(String dados); } Normalmente as interfaces são implementadas por classes que participam de processos maiores, algo muito comum no uso de frameworks. Podemos utilizar a interface do exemplo para a de�nição de um processo cadastral com o uso de dados criptografados, em que cada classe envolvida pode adotar um algoritmo de criptogra�a próprio. Ao implementar os métodos de uma interface, as classes passam a ser reconhecidas por qualquer ferramental em que ela é utilizada, adaptando todo o processo às funcionalidades especí�cas de�nidas nessas classes. public class GestorCripto { public void imprimir(CriptoData objetoCripto){ System.out.println(objetoCripto.decripto()); } } Note que um tipo de interface pode ser utilizado como parâmetro de um método, e poderá aceitar uma instância de qualquer classe que implemente a interface, sem a necessidade de pertencer a uma família especí�ca. Para utilizar uma interface é necessária a palavra implements, no lugar de extends, e devemos implementar todos os métodos previstos. import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class Coordenadas implements CriptoData{ private static final String chave = "0123456789ABCDEF"; private byte[] latlong = null; @Override public String decripto() { if(latlong==null) return null; try { Cipher c = Cipher.getInstance("AES"); SecretKey k = new SecretKeySpec(chave.getBytes(),"AES"); c.init(Cipher.DECRYPT_MODE, k); return new String(c.doFinal(latlong)); }catch(Exception e){ return null; } } @Override public void cripto(String dados) { try { Cipher c = Cipher.getInstance("AES"); SecretKey k = new SecretKeySpec(chave.getBytes(),"AES"); c.init(Cipher.ENCRYPT_MODE, k); System.out.println(new String(latlong = c.doFinal(dados.getBytes()))); }catch(Exception e){ latlong = null; } } } Nessa implementação da interface é utilizada a biblioteca de criptogra�a padrão do Java e uma chave �xa, de 16 bytes. No método cripto também é impresso o valor criptografado, mas isso não seria necessário, sendo feito dessa forma apenas para acompanhamento visual durante a execução. Os métodos são bastante parecidos, com a inicialização da chave por meio de SecretKeySpec, e do encriptador por meio de getInstance, devendo ser utilizado o mesmo algoritmo em ambos, no caso AES. O método doFinal faz a criptogra�a ou decriptogra�a, de acordo com o modo de inicialização. public class TesteCripto { public static void main(String[] args) { GestorCripto gestor = new GestorCripto(); Coordenadas coord = new Coordenadas(); coord.cripto("22.5N12.5W"); gestor.imprimir(coord); } } No programa de teste nós temos uma instância de GestorCripto e outra de Coordenadas. Com a inicialização do valor das coordenadas por meio de cripto, ocorre a criptogra�a do valor 22.5N12.5W e armazenagem no atributo interno latlong, além da impressão do valor criptografado, e na chamada ao método imprimir, do gestor, o valor de latlong é recuperado por meio de decripto e impresso no console. Podemos deixar a classe de gerência mais genérica, com a utilização de valores planos e criptografados, desde que o parâmetro seja do tipo Object. Nesse caso será necessário testar a presença da interface com instanceof, mesmo operador utilizado para classes, e efetuar a conversão de tipo, de forma a acessar os métodos da interface, segundo o que é conhecido como type cast. public class GestorCripto { public void imprimir(Object objetoCripto){ String valor = (objetoCripto instanceof CriptoData)? ((CriptoData)objetoCripto).decripto(): objetoCripto.toString(); System.out.println(valor); } } O processo de type cast no Java é muito simples, necessitando apenas do tipo de destino entre parênteses. Com essa modi�cação, se objetoCripto for do tipo CriptoData, ele é convertido para o tipo correto e o valor é obtido com o uso de decripto, enquanto para os demais é utilizado toString. Embora muitos confundam as interfaces com classes abstratas, as diferenças em termos metodológicos são muito grandes. Enquanto as classes abstratas de�nem uma regra para sua família de classes, as interfaces podem ser implementadas por qualquer classe que necessite participar de algum processo especí�co. Atenção O Java aceita apenas herança simples, ao contrário de linguagens como C++, em que é possível herdar de várias classes simultaneamente, mas ele permite implementar quantas interfaces forem necessárias, o que viabiliza a participação em múltiplos processos. O uso de criptogra�a nos obrigou a adotar uma estrutura de tratamento de exceções, assunto que será abordado na próxima aula. Modelagem comportamental Nem sempre conseguimos expressar os elementos do mundo físico apenas em termos de classes. Por exemplo, somos capazes de de�nir uma classe Passaro, e instanciar todos os objetos que forem necessários, mas como podemos de�nir uma revoada? Para situações desse tipo, nosso foco não deve ser nos objetos individuais, mas no comportamento do grupo, levando a um novo nível de abstração, em que modelamos comportamentos genéricos, aplicáveis a qualquer classe, pertencente a um grupo especí�co ou não. No exemplo inicial, poderíamos de�nir o comportamento de revoada e aplicá-lo a qualquer tipo de pássaro, mas nada impediria que fosse aproveitado para outros tipos de animais que voam, como os morcegos.Poderíamos fazer o mesmo tipo de consideração acerca das �las. Você pode ter uma sequência de pessoas, elefantes, carros, ou qualquer outra coisa, e todas essas sequências organizadas seriam �las, independentemente dos objetos que as constituem. Fonte: Shuttershock por Allen McDavid Stoddard Essa generalização também pode ser feita para uma pilha de pratos ou de livros, pois não importa de qual tipo de objeto se trata, e sim que eles estejam empilhados. Na verdade, Filas e Pilhas são estruturas de dados clássicas, que utilizam ponteiros nas implementações estruturadas, ou abordagens semelhantes na programação orientada a objetos, mas que podem ser generalizadas com a utilização de modelagem comportamental. Para esse tipo de abordagem temos dois modelos de implementação no Java: Classes Genéricas e Anotações. Classes genéricas e Anotações Clique no botão acima. Classes genéricas As classes genéricas, também chamadas de classes template, já tinham sido idealizadas no início da orientação a objetos, e eram comuns em linguagens como o C++, mas vieram para o Java em versões um pouco mais recentes, sendo conhecidas como Generics. Uma classe genérica modela um comportamento com lacunas bem de�nidas e qualquer classe pode ser utilizada para o preenchimento dessas lacunas, de�nindo a funcionalidade do processo de forma concreta. Nesse exemplo foi de�nida uma classe genérica de Pilha, com os métodos para empilhar e desempilhar (push/pop), em que pode ser observada a presença da lacuna de�nida pela letra K. Na verdade poderia ser qualquer letra, devendo apenas ser colocada junto ao nome da classe. A letra K representa uma classe qualquer, e será substituída em todas as ocorrências posteriores, quando de�nirmos o tipo de objeto que será utilizado. Por exemplo, se utilizarmos String, o método empilhar poderia ser lido da forma apresentada a seguir. É interessante observar também a de�nição de uma inner class chamada NoPilha, ou seja, uma classe utilitária interna, que só pode ser utilizada na programação da classe Pilha. Na prática ela auxilia na construção de uma estrutura de pilha típica, em que um valor é colocado no topo, e deve apontar para o elemento logo abaixo na pilha (proximo). public class Pilha<K> { class NoPilha K dado; NoPilha proximo; } private NoPilha<K> topo = null; public void empilhar(K dado){ NoPilha<K> novo = new NoPilha<>(); novo.dado = dado; novo.proximo = topo; topo = novo; } public K desempilhar(){ if(topo==null) return null; NoPilha<K> antigo = topo; topo = topo.proximo; return antigo.dado; } } public void empilhar(String dado){ NoPilha novo = new NoPilha<>(); novo.dado = dado; novo.proximo = topo; topo = novo; } Por se tratar de uma estrutura LIFO, o último elemento empilhado é o primeiro a ser recuperado, como em uma pilha de pratos, em que não seria possível pegar o prato de baixo sem que os demais caíssem. Nesse exemplo temos uma Pilha de Integer, classe wrapper para int, que permitirá o empilhamento de valores inteiros. Após o empilhamento de alguns números, eles são desempilhados na variável x e impressos, até que o valor obtido seja igual a null, sendo apresentados na ordem inversa da que foi utilizada na entrada. Classes genéricas são amplamente utilizadas pelo framework de coleções do Java, sendo comum a utilização de ArrayList na de�nição de conjuntos de entidades. Ao contrário dos vetores, nas coleções não estamos restritos a uma quantidade �xa de elementos, e ainda podemos utilizar o comando for no estilo foreach. As coleções funcionam internamente como estruturas de listas encadeadas, o que justi�ca a �exibilidade com relação à quantidade de elementos. Note que no comportamento padrão, os elementos da lista são varridos na ordem de entrada, conforme você poderá veri�car com a execução do exemplo. Anotações Em algumas linguagens, como o Java, os objetos apresentam informações que permitem o reconhecimento da estrutura interna sem a prévia de�nição da classe. Essa característica é chamada de re�exividade computacional, trazendo dados relevantes acerca do funcionamento da classe. Fonte: tutorialspoint.com. public class TestePilha { public static void main(String[] args) { Pilha pilha1 = new Pilha<>(); pilha1.empilhar(5); pilha1.empilhar(7); pilha1.empilhar(9); Integer x; while((x=pilha1.desempilhar())!=null) System.out.println(x); } } import java.util.ArrayList; public class TesteColecao { public static void main(String[] args) { ArrayList lista = new ArrayList<>(); lista.add(5); lista.add(7); lista.add(9); for(Integer i: lista) System.out.println(i); } } javascript:void(0); Com o uso de classes re�exivas, os frameworks conseguem buscar qualquer informação necessária para a execução dos processos, sem que o objeto seja conhecido previamente, o que permite toda a generalização obtida em diversas ferramentas do mercado. O princípio de re�exividade do Java foi de�nido de forma muito simples, pois os objetos derivam direta ou indiretamente da classe Object, e toda a de�nição estrutural é oferecida a partir de outra classe, chamada Class. Não confunda a palavra class (minúsculo), para a de�nição de classes, com a classe Class (maiúsculo), para a gerência de classes. Com o uso de Class podemos instanciar objetos a partir do nome completo da classe em modo texto, obter e manipular atributos em objetos Field, e acessar os métodos com objetos Method e Parameter. Note que nesse exemplo o objeto da classe Pessoa é criado a partir do nome completo, incluindo o pacote, no formato texto, e conseguimos extrair os seus atributos com objetos Field. Qualquer nome de classe que fosse utilizado iria funcionar e mostraria os atributos de�nidos ao nível da classe. Também podemos observar a captura dos setters e do método exibir, com a chamada a getMethod, passando o nome e tipo dos parâmetros, em que o retorno ocorre em objetos Method. Esses objetos são acionados com o uso de invoke, sendo passados o objeto e os valores dos parâmetros, o que proporciona uma chamada indireta aos métodos. Com esse exemplo, nós podemos observar a manipulação de uma instância de Pessoa, sem a utilização de uma codi�cação direta. Nos frameworks Java são utilizados processos semelhantes, con�gurados a partir de arquivos no modo texto, como XML ou JSON. A re�exividade computacional é a base funcional das anotações, o segundo tipo de modelagem comportamental. Elas trabalham com metadados que podem ser reconhecidos por ferramentas externas. Essa é uma modelagem comportamental que segue o caminho inverso da anterior, pois nas classes genéricas aplicamos o objeto ao comportamento, enquanto nas anotações aplicamos o comportamento aos objetos. Por exemplo, temos uma pilha de objetos, ou o objeto se comporta como uma entidade de banco. import java.lang.reflect.Field; import java.lang.reflect.Method; public class TesteReflexividade { public static void main(String[] args) throws Exception { Class classe = Class.forName("poo.Pessoa"); Object objeto = classe.getConstructor().newInstance(); for(Field f: classe.getDeclaredFields()) System.out.println("Campo: "+f.getName()); Method sNome = classe.getMethod("setNome", String.class); Method sTelefone = classe.getMethod("setTelefone", String.class); Method exibir = classe.getMethod("exibir"); sNome.invoke(objeto, "Maria"); sTelefone.invoke(objeto, "3333-3333"); exibir.invoke(objeto); } } Para de�nir uma anotação, precisamos utilizar o comando @interface, além de de�nir a utilização desta com @Target, que nesse exemplo indica que a anotação será voltada para classes, mas que poderiam ser diversos outros tipos, como métodos e atributos, bem como o escopo de utilização com @Retention, colocado aqui como em tempo de execução. Para essa anotação criamos três campos, sendo que dois são de preenchimento obrigatório e um opcional, no caso empresa, já que é de�nido um valor padrão com default. Após criar nossa interface de anotação, podemos aplicá-la a uma classe.Com nossas anotações de�nidas e aplicadas às classes de interesse, vamos criar um processo que as utilize, da mesma forma que os frameworks. Inicialmente de�nimos um vetor de objetos, recebendo um Carro e uma String, e nesse caso apenas o Carro traz a anotação de Autoria. Ao percorrer o vetor, para cada Object dele iremos obter o Class, e em seguida perguntar se a anotação de Autoria está presente com o uso de isAnnotationPresent. Estando presente, a anotação é capturada, sendo convertida para o tipo correto, e os dados podem ser impressos no console. Aqui nós apenas exibimos os dados da anotação, mas o JPA utiliza esses dados para efetuar o mapeamento entre a entidade de classe e a tabela do banco de dados, como veremos no decorrer da disciplina. import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(value=RetentionPolicy.RUNTIME) @Target(value=ElementType.TYPE) public @interface Autoria { String autor(); int ano(); String empresa() default "UNESA"; } @Autoria(autor = "Denis", ano = 2020) public class Carro { } Object[] objetos = {new Carro(), "XPTO" }; for(Object obj: objetos){ Class c1 = obj.getClass(); if(c1.isAnnotationPresent(Autoria.class)){ Autoria a1 = (Autoria)c1.getAnnotation(Autoria.class); System.out.println("Classe " + c1.getName() + " escrita por " + a1.autor() + " em " + a1.ano()); } } Atividades 1. Qual das características da Orientação a Objetos permite a alteração funcional de um método herdado, proporcionando grande �exibilidade e adaptabilidade ao ambiente? a) Sobrecarga b) Construtores c) Encapsulamento d) Polimorfismo e) Destrutores 2. Você criou um sistema de �nanças com um núcleo de cálculo adaptável, e as classes que desejam utilizar as funcionalidades desse núcleo podem pertencer a qualquer família, mas devem implementar um conjunto de métodos. Qual elemento de programação você deverá utilizar para viabilizar essa funcionalidade? a) Interface b) Classe abstrata c) Classe concreta d) Herança e) Sobrecarga 3. Segundo a sintaxe do Java, qual a palavra utilizada para indicar que uma classe é descendente de outra? a) Extends b) Implements c) Super d) This e) Private 4. Qual a palavra reservada da sintaxe Java que permite a manutenção da semântica em setters e construtores? a) Class b) Public c) New d) This e) Protected 5. Você desenvolveu um sistema e descobriu que as atividades de inserção, exclusão e consulta aos dados era repetitiva em termos de programação. Com isso resolveu generalizar a solução em vez de criar dezenas de classes similares. Baseado no esqueleto de uma das classes, crie uma solução com classe genérica. public class PessoaDAO { public void inserir(Pessoa entidade){ } public void excluir(Integer chave){ } public ArrayList<Pessoa> buscarTodos( ){ } } Notas Título modal 1 Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Título modal 1 Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Referências CORNELL, G.; HORSTMANN, C. Core Java. 8. ed. São Paulo: Pearson, 2010. DEITEL, P.; DEITEL, H. Java: como programar. 8. ed. São Paulo: Pearson, 2010. FONSECA, E. Desenvolvimento de software. Rio de Janeiro: Estácio, 2015. SANTOS, F. Programação I. Rio de Janeiro: Estácio, 2017. Próxima aula Controle de exceções; Manipulação de coleções; De�nição de entidades e relacionamentos. Explore mais Leia os textos: Padrões de design Como criar um pool de objetos genéricos em Java javascript:void(0); javascript:void(0);
Compartilhar