CodeGym /Blogue Java /Random-PT /Quais problemas o padrão de projeto do adaptador resolve?...
John Squirrels
Nível 41
San Francisco

Quais problemas o padrão de projeto do adaptador resolve?

Publicado no grupo Random-PT
O desenvolvimento de software é dificultado por componentes incompatíveis que precisam trabalhar juntos. Por exemplo, se você precisar integrar uma nova biblioteca com uma plataforma antiga escrita em versões anteriores do Java, poderá encontrar objetos incompatíveis, ou melhor, interfaces incompatíveis. Quais problemas o padrão de projeto do adaptador resolve?  - 1O que fazer neste caso? Reescrever o código? Não podemos fazer isso, porque a análise do sistema levará muito tempo ou a lógica interna do aplicativo será violada. Para resolver esse problema, o padrão adaptador foi criado. Ajuda objetos com interfaces incompatíveis a trabalharem juntos. Vamos ver como usá-lo!

Mais sobre o problema

Primeiro, vamos simular o comportamento do sistema antigo. Suponha que isso gere desculpas para chegar atrasado ao trabalho ou à escola. Para fazer isso, ele possui uma Excuseinterface que possui generateExcuse(), likeExcuse()e dislikeExcuse()métodos.

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
A WorkExcuseclasse implementa esta interface:

public class WorkExcuse implements Excuse {
   private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
Vamos testar nosso exemplo:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Saída:

"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
Agora imagine que você lançou um serviço gerador de desculpas, coletou estatísticas e percebeu que a maioria de seus usuários são estudantes universitários. Para atender melhor esse grupo, você pediu a outro desenvolvedor que criasse um sistema que gerasse desculpas especificamente para universitários. A equipe de desenvolvimento realizou pesquisas de mercado, classificou as desculpas, conectou alguma inteligência artificial e integrou o serviço a relatórios de tráfego, boletins meteorológicos e assim por diante. Agora você tem uma biblioteca para gerar desculpas para universitários, mas com uma interface diferente: StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Essa interface possui dois métodos: generateExcuse, que gera uma desculpa, e dislikeExcuse, que evita que a desculpa apareça novamente no futuro. A biblioteca de terceiros não pode ser editada, ou seja, você não pode alterar seu código-fonte. O que temos agora é um sistema com duas classes que implementam a Excuseinterface e uma biblioteca com uma SuperStudentExcuseclasse que implementa a StudentExcuseinterface:

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
O código não pode ser alterado. A hierarquia de classes atual se parece com isso: Quais problemas o padrão de projeto do adaptador resolve?  - 2Esta versão do sistema só funciona com a interface Excuse. Você não pode reescrever o código: em um aplicativo grande, fazer essas alterações pode se tornar um processo demorado ou interromper a lógica do aplicativo. Poderíamos introduzir uma interface base e expandir a hierarquia: Quais problemas o padrão de projeto do adaptador resolve?  - 3Para fazer isso, temos que renomear a Excuseinterface. Mas a hierarquia extra é indesejável em aplicações sérias: a introdução de um elemento raiz comum quebra a arquitetura. Você deve implementar uma classe intermediária que nos permitirá usar a funcionalidade nova e antiga com perdas mínimas. Resumindo, você precisa de um adaptador .

O princípio por trás do padrão adaptador

Um adaptador é um objeto intermediário que permite que as chamadas de método de um objeto sejam compreendidas por outro. Vamos implementar um adaptador para nosso exemplo e chamá-lo de Middleware. Nosso adaptador deve implementar uma interface compatível com um dos objetos. Deixe estar Excuse. Isso permite Middlewarechamar os métodos do primeiro objeto. Middlewarerecebe chamadas e as encaminha de forma compatível para o segundo objeto. Aqui está a Middlewareimplementação com os métodos generateExcusee dislikeExcuse:

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
Teste (no código do cliente):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
Saída:

An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
Uma desculpa incrível adaptada às atuais condições meteorológicas, engarrafamentos ou atrasos nos horários dos transportes públicos. O generateExcusemétodo simplesmente passa a chamada para outro objeto, sem nenhuma alteração adicional. O dislikeExcusemétodo exigia que primeiro colocássemos a desculpa na lista negra. A capacidade de executar o processamento intermediário de dados é uma razão pela qual as pessoas adoram o padrão do adaptador. Mas e quanto ao likeExcusemétodo, que faz parte da Excuseinterface, mas não faz parte da StudentExcuseinterface? A nova funcionalidade não suporta esta operação. O UnsupportedOperationExceptionfoi inventado para esta situação. Ele é lançado se a operação solicitada não for suportada. Vamos usá-lo. Esta é a Middlewareaparência da nova implementação da classe:

public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
À primeira vista, esta solução não parece muito boa, mas imitar a funcionalidade pode complicar a situação. Se o cliente prestar atenção e o adaptador estiver bem documentado, tal solução é aceitável.

Quando usar um adaptador

  1. Quando você precisa usar uma classe de terceiros, mas sua interface é incompatível com o aplicativo principal. O exemplo acima mostra como criar um objeto adaptador que agrupa chamadas em um formato que um objeto de destino possa entender.

  2. Quando várias subclasses existentes precisam de alguma funcionalidade comum. Em vez de criar subclasses adicionais (o que levará à duplicação de código), é melhor usar um adaptador.

Vantagens e desvantagens

Vantagem: O adaptador oculta do cliente os detalhes do processamento de solicitações de um objeto para outro. O código do cliente não pensa em formatar dados ou manipular chamadas para o método de destino. É muito complicado e os programadores são preguiçosos :) Desvantagem: A base de código do projeto é complicada por classes adicionais. Se você tiver muitas interfaces incompatíveis, o número de classes adicionais pode se tornar incontrolável.

Não confunda adaptador com fachada ou decorador

Com apenas uma inspeção superficial, um adaptador pode ser confundido com os padrões da fachada e do decorador. A diferença entre um adaptador e uma fachada é que uma fachada introduz uma nova interface e envolve todo o subsistema. E um decorador, ao contrário de um adaptador, altera o próprio objeto em vez da interface.

Algoritmo passo a passo

  1. Primeiro, certifique-se de ter um problema que esse padrão possa resolver.

  2. Defina a interface do cliente que será usada para interagir indiretamente com objetos incompatíveis.

  3. Faça com que a classe do adaptador herde a interface definida na etapa anterior.

  4. Na classe do adaptador, crie um campo para armazenar uma referência ao objeto adaptee. Essa referência é passada para o construtor.

  5. Implemente todos os métodos de interface do cliente no adaptador. Um método pode:

    • Repasse as chamadas sem fazer nenhuma alteração

    • Modifique ou complemente dados, aumente/diminua o número de chamadas para o método de destino, etc.

    • Em casos extremos, se um determinado método permanecer incompatível, lance um UnsupportedOperationException. As operações sem suporte devem ser estritamente documentadas.

  6. Se o aplicativo usar apenas a classe do adaptador por meio da interface do cliente (como no exemplo acima), o adaptador poderá ser expandido facilmente no futuro.

É claro que esse padrão de design não é uma panacéia para todos os males, mas pode ajudá-lo a resolver com elegância o problema de incompatibilidade entre objetos com diferentes interfaces. Um desenvolvedor que conhece os padrões básicos está vários passos à frente daqueles que só sabem escrever algoritmos, porque os padrões de projeto são necessários para criar aplicativos sérios. A reutilização de código não é tão difícil e a manutenção torna-se agradável. Isso é tudo por hoje! Mas em breve continuaremos a conhecer vários padrões de design :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION