Padrão de Projeto: Adapter

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /Padrão de Projeto: Adapter

Padrão de Projeto: Adapter

Vinícius Thiengo22/06/2016
(1166) (10)
Go-ahead
"O que as pessoas devem tentar fazer é trabalhar no que gostam, procurando áreas em que sua vocação e talento façam diferença. É fundamental ser paciente. É vital manter o otimismo sempre."
Carlos Slim
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas934
Acessar Livro
Código limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas598
Acessar Livro
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Opa, blz?

Nesse artigo vamos falar sobre o padrão de projeto Adapter, para, além de apresentar o padrão, poder abordar em artigos mais atuais os métodos de refatoração: Extrair Adapter e Unificar Interfaces Com Adapter.

Tópicos presentes no artigo:

Apresentação

O padrão de projeto proposto aqui é utilizado quando há a necessidade de trabalhar com classes distintas respondendo a uma interface comum no código.

Vamos iniciar com a definição formal do padrão:

Converte a interface de uma classe para outra interface que o código cliente espera encontrar. A entidade adaptadora permite que classes com interfaces incompatíveis trabalhem juntas.

Ainda não está muito claro, certo. No decorrer do conteúdo, com o exemplo que será apresentado, o entendimento do padrão será tranquilo.

Diagrama

Vamos ao diagrama do padrão proposto. Note que o Adapter, como o padrão Cláusula de Guarda, está entre os padrões mais simples de entender e utilizar:

A Interface Alvo na verdade pode ser uma classe, pois a ideia de interface aqui é no contexto geral, ou seja, que defini métodos e atributos necessários (Interface, classe concreta ou classe abstrata).

A classe adaptadora que vai permitir o trabalho com uma interface comum é a Adaptador do diagrama acima, ela implementa a interface Alvo, que permite o Cliente utilizar, além de outros objetos que já implementam essa Interface, os objetos do tipo Adaptado. Objetos do tipo Adaptador têm objetos da classe Adaptado sendo integrados por meio de composição.

No padrão Adapter criamos uma classe adaptadora para cada versão necessária que permite comunicação entre interfaces distintas. Caso uma classe adaptadora suporte mais do que apenas uma versão de classes distintas, chamamos essa configuração de classe adaptadora sobrecarregada, algo que na verdade é ruim para o projeto.

Código de exemplo

O exemplo a seguir utiliza duas classes e interfaces distintas, são elas: PatoMarreco e PeruAustraliano em classes; e Pato e Peru em Interfaces. Seguem os códigos. Começando pela Interface Pato:

public interface Pato {
public void grasnar();
public void voar();
}

 

Logo depois a classe PatoMarreco:

public class PatoMarreco implements Pato {
    @Override
public void grasnar() {
System.out.print("Quack, quack, quack.");
}

@Override
public void voar() {
System.out.print("Voar, voar, voar, voar, voar");
}
}

 

Então a Interface Peru:

public interface Peru {
public void soar();
public void voar();
}

 

E por fim a classe PeruAustraliano:

public class PeruAustraliano implements Peru {
@Override
public void soar() {
System.out.print("Brulu, brulu, brulu.");
}

@Override
public void voar() {
System.out.print("Voar.");
}
}

 

Temos agora um código cliente que precisa utilizar todas as entidades (Pato e Peru) pela interface de Pato (métodos públicos grasnar() e voar()). Segue trecho do código cliente:

public class ClientePato {
public static void main( String[] args ){
PatoMarreco pato = new PatoMarreco();
PeruAustraliano peru = new PeruAustraliano();

/* TODO */
}
}

 

Uma opção seria modificar as classes para trabalharmos com herança, ou até mesmo uma Interface comum sendo implementada em ambas as classes, PatoMarreco e PeruAustraliano. Poderia ser uma boa escolha, porém a parte do "modificar" é que não se encaixa.

Precisamos de uma maneira de expandir o código que permita a continua evolução do software sem precisar de alterar estruturas de classes e algoritmos clientes. Uma boa maneira de conseguir essas objetivos é programando, ao menos, na linha dos princípios "Programe para Interface ao invés de para implementação" e "Prefira composição ao invés de herança".

No algoritmo anterior não é fácil perceber algo de crítico com a adição de uma nova superclasse, porém temos de ter em mente que programas não são construídos com poucas linhas de código e modificar um projeto com milhares de linhas (quando ele é pequeno) é muito mais custoso que apenas expandi-lo.

Quando trabalhando com expansão não há necessidade de verificar quais partes do código tem XYZ referências a nossas entidades expandidas, pois os códigos existentes continuam os mesmos.

Com isso, apresentados os problemas da modificação, o que nos resta nesse projeto é expandi-lo utilizando o padrão Adapter. Sabendo que o código cliente vai utilizar a Interface Pato, essa é então nossa entidade alvo.

A classe que será adaptada é a PeruAustraliano. Nossa classe adapter se chamará PeruAdapter.

Não seria melhor PatoAdapter? Até porque estaríamos adaptando para ser um Pato.

A princípio não. Vamos assumir que teríamos também uma classe PassaroPardal que implementa Passaro.

Seguindo nosso código cliente, os objetos dessa Interface Passaro também deveriam responder a interface Pato, porém tendo em mente que as classes PeruAustraliano e PassaroPardal não compartilham nem Interface e nem superclasse, a classe PatoAdapter teria de ter condicionais para instâncias de PeruAustraliano e PassaroPardal. Tornando-se assim, a já informada, classe adaptadora sobrecarregada.

Logo, PatoAdapter, Não é uma boa escolha. Classes adaptadoras PeruAdapter e PassaroAdapter já seriam mais que o suficiente, respeitando o padrão Adapter e os princípios de orientação a objetos citados anteriormente.

Ok, vamos ao código da classe PeruAdapter:

public class PeruAdapter implements Pato{
private Peru peru;

public PeruAdapter( Peru peru ){
this.peru = peru;
}

@Override
public void grasnar() {
peru.soar(); /* MÉTODO DE PERU SENDO UTILIZADO DE FORMA ADAPTADA */
}

@Override
public void voar() {
peru.voar(); /* IGUALMENTE AQUI */
}
}

 

Simples, não?! A entidade do tipo Peru entra na integração em PeruAdapter via composição. Com a classe adaptadora conseguimos responder ao código cliente sem a necessidade de modificar nenhuma entidade já presente no projeto. Segue código cliente atualizado:

public class ClientePato {
public static void main( String[] args ){
PatoMarreco pato = new PatoMarreco();
PeruAustraliano peru = new PeruAustraliano();

PeruAdapter peruAdapter = new PeruAdapter( peru );
Pato[] patos = {pato, peruAdapter};

for( Pato p : patos ){
p.grasnar();
System.out.println();
p.voar();
System.out.println();
}
}
}

 

Executando o código anterior temos como saída:

Note que alguns métodos de nossa classe adaptadora podem ser alterados para os resultados serem mais coerentes ao contexto em que as classes adaptadas estão sendo utilizadas. No caso acima, o Peru voa bem menos que um Pato. Para que esse comportamento em Peru fosse similar ao do Pato poderíamos realizar a seguinte alteração em nossa classe adaptadora:

public class PeruAdapter implements Pato{
...

@Override
public void voar() {
peru.voar();
System.out.print(", voar, voar, voar, voar");
}
}

 

Rodando o código cliente novamente, teríamos a seguinte saída:

Você provavelmente deve estar se perguntando: e quando nossa classe adaptada não tiver como mapear alguns dos métodos de nosso adaptador?

Nesse caso podemos criar uma implementação própria desses métodos dentro da classe adaptadora, não há problemas. Vamos assumir que nossa Interface Pato tivesse também uma assinatura de método mergulho():

public interface Pato {
public void grasnar();
public void voar();
public void mergulho();
}

 

Nossa classe adaptadora poderia simplesmente retornar que o mapeamento não é suportado, algo que é comum, mas não uma regra. Veja no código a seguir como ficaria:

public class PeruAdapter implements Pato {
...

@Override
public void mergulho() {
throw new UnsupportedOperationException("Método mergulho() não suportado por Perus");
}
}

 

Com isso quase finalizamos a explicação sobre o padrão Adapter.

Quase?

Sim. Na verdade somente queria lhe informar que há uma outra variação desse padrão, uma que trabalha exclusivamente com heranças múltiplas, ou seja, você terá de estar trabalhando com uma linguagem que suporta esse tipo de sintaxe. Segue o diagrama da outra versão de Adapter:

Note que mesmo com a versão alternativa trabalhando somente com herança, no caso herança dupla. Podemos sem problemas trabalhar com herança na versão mais comum do padrão. Lembrando do termo “interface” esta sendo utilizado aqui no contexto geral em programação.

Provavelmente esse papo de "versão comum" e "versão alternativa" não ficou muito intuitivo. Logo, corrigindo: a versão comum na verdade é conhecida com o nome “Adaptador de Objetos”. A versão alternativa apresentada a pouco se chama “Adaptador de Classes”.

O importante até aqui é entender o conceito do padrão Adapter, mesmo que resumido: uma classe implementando uma interface permitindo que objetos de interfaces distintas possam ser utilizados por códigos clientes que necessitam de uma interface comum a todos os objetos. Ou seja, tanto implements Interface quanto extends Classe atenderiam ao padrão.

Fique atento:

Aqui já definimos o que é o padrão Adapter. Porém, quando você estiver estudando padrões de projeto também por outras literaturas, você provavelmente vai se deparar com um padrão chamado Facade (ou Fachada).

Esse padrão pode fazer com que você o entenda como sendo uma variação do padrão Adapter, quando não é.

O Facade é utilizado para simplificar o acesso a um subsistema, ou seja, não necessariamente envolve implementações de Interfaces ou heranças. Ele é muito comum para integrar código legado com código mais atual.

O Adapter é utilizado para adaptar o acesso a objetos de interfaces distintas por meio de uma interface requerida pelo código cliente.

Curiosidade:

O padrão Adapter tende a ser implementado em código já existente que realmente precisa de uma adaptação onde herança não é a melhor escolha, ou seja, ele é quase um método de refatoração. Padrões como Observer e Factory tendem a ser implementados desde o início do projeto.

Pontos negativos

  • Cair na tentação de suportar mais de uma versão em uma classe adaptadora é, ao menos, o problema de leitura de código criado, prejudicando a fácil evolução dele;
  • Pode ter uma implementação muito longa caso o código cliente trabalhe com muitas instâncias diferentes, porém com interface comum.

Pontos positivos

  • Como é comum com todos os outros padrões de projeto, emprega uma linguagem universal no algoritmo, linguagem de entendimento comum entre outros programadores, melhorando ainda mais a legibilidade do código;
  • Modulariza o projeto ainda mais, evitando condicionais grandes e complexas.

Conclusão

Com o padrão Adapter você consegue rapidamente criar um modo comum de comunicação entre objetos de classes clientes e objetos de classes de domínio do problema, porém fique atento para não utilizar adapters sobrecarregados, pois algo que pode parecer “inteligente” por ter somente uma classe adaptadora, pode acabar se tornando o gargalo da evolução do software. Lembrando que sempre que seu código tiver de utilizar scripts de identificação de tipo, isso é um forte indício de que ele deve ser refatorado.

Fontes

Use a Cabeça! Padrões de Projetos

Vlw.

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

Padrão de Projeto: ObserverPadrão de Projeto: ObserverAndroid
Padrão de Projeto: Simple FactoryPadrão de Projeto: Simple FactoryAndroid
Padrão de Projeto: Factory MethodPadrão de Projeto: Factory MethodAndroid
Padrão de Projeto: Abstract FactoryPadrão de Projeto: Abstract FactoryAndroid

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...