JavaRush /Blogue Java /Random-PT /Que problemas o padrão de design do Adaptador resolve?

Que problemas o padrão de design do Adaptador resolve?

Publicado no grupo Random-PT
O desenvolvimento de software costuma ser complicado pela incompatibilidade entre componentes que funcionam entre si. Por exemplo, se você precisar integrar uma nova biblioteca com uma plataforma antiga escrita em versões anteriores de Java, poderá encontrar incompatibilidade de objetos, ou mais precisamente, de interfaces. O que fazer neste caso? Reescrever o código? Mas isso é impossível: a análise do sistema levará muito tempo ou a lógica interna do trabalho será quebrada. Quais problemas o padrão de design do Adaptador resolve - 1Para resolver esse problema, eles criaram o padrão Adapter, que ajuda objetos com interfaces incompatíveis a trabalharem juntos. Vamos ver como usá-lo!

Mais detalhes sobre o problema

Primeiro, vamos simular o comportamento do sistema antigo. Digamos que gere motivos para chegar atrasado ao trabalho ou à escola. Para fazer isso, temos uma interface que contém Excuseos métodos generateExcuse()e likeExcuse().dislikeExcuse()
public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Esta interface é implementada pela classe WorkExcuse:
public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все How в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};

   @Override
   public String generateExcuse() { // Случайно выбираем отговорку из массива
       String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
               sorryOptions[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Дублируем элемент в массиве, чтобы шанс его выпадения был выше
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Удаляем элемент из массива
   }
}
Vamos testar o exemplo:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Conclusão:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
Agora vamos imaginar que você lançou o serviço, coletou estatísticas e percebeu que a maioria dos usuários do serviço são estudantes universitários. Para melhorá-lo para as necessidades deste grupo, você encomendou um sistema de geração de desculpas de outro desenvolvedor especificamente para ela. A equipe de desenvolvimento conduziu pesquisas, compilou classificações, conectou inteligência artificial, adicionou integração com engarrafamentos, clima e assim por diante. Agora você tem uma biblioteca para gerar desculpas para os alunos, mas a interface para interagir com ela é diferente - StudentExcuse:
public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
A interface possui dois métodos: generateExcuse, que gera uma desculpa, e dislikeExcuse, que bloqueia a desculpa para que ela não apareça no futuro. Uma biblioteca de terceiros está fechada para edição - você não pode alterar seu código-fonte. Como resultado, em seu sistema existem duas classes que implementam a interface Excusee uma biblioteca com uma classe SuperStudentExcuseque implementa a interface StudentExcuse:
public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
O código não pode ser alterado. O esquema atual ficará assim: Que problemas o padrão de design Adapter - 2 resolve?Esta versão do sistema só funciona com a interface Excuse. Você não pode reescrever o código: em um aplicativo grande, essas alterações podem demorar muito ou interromper a lógica do aplicativo. Você pode sugerir a introdução da interface principal e o aumento da hierarquia: Quais problemas o padrão de design do Adaptador resolve - 3Para fazer isso, você precisa renomear a interface Excuse. Mas a hierarquia adicional é indesejável em aplicações sérias: a introdução de um elemento raiz comum quebra a arquitetura. Deve ser implementada uma classe intermediária que permitirá o uso de funcionalidades novas e antigas com perdas mínimas. Resumindo, você precisa de um adaptador .

Como funciona o padrão Adaptador

Um adaptador é um objeto intermediário que torna as chamadas aos métodos de um objeto compreensíveis para 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. Graças a isso, Middlewareele pode chamar métodos do primeiro objeto. Middlewarerecebe chamadas e as passa para o segundo objeto em um formato compatível. Esta é a aparência da implementação de um método Middlewarecom métodos generateExcusee dislikeExcuse:
public class Middleware implements Excuse { // 1. Middleware становится совместимым с an objectом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый an object
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
Teste (no código do cliente):
public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются an objectы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в an object-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
Conclusão:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
Uma desculpa incrível, adaptada ao estado atual do tempo, engarrafamentos ou perturbações nos horários dos transportes públicos. O método generateExcusesimplesmente transfere a chamada para outro objeto, sem transformações adicionais. O método dislikeExcuseexigia primeiro colocar a desculpa em uma lista negra de banco de dados. O processamento adicional de dados intermediários é a razão pela qual o padrão Adapter é adorado. Mas e quanto a um método likeExcuseque está na interface Excuse, mas não na StudentExcuse? Esta operação não é suportada na nova funcionalidade. Para este caso, eles criaram uma exceção UnsupportedOperationException: ela será lançada se a operação solicitada não for suportada. Vamos usar isso. Esta é a aparência da nova implementação da classe Middleware:
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("Метод likeExcuse не поддерживается в новом функционале");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Метод обращается за дополнительной информацией к БД,
       // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
   }
}
À primeira vista, esta solução não parece bem-sucedida, mas simular a funcionalidade pode levar a uma situação mais complexa. Se o cliente estiver atento e o adaptador estiver bem documentado, esta solução é aceitável.

Quando usar o adaptador

  1. Se precisar usar uma classe de terceiros, mas sua interface não é compatível com o aplicativo principal. O exemplo acima mostra como é criado um objeto shim que agrupa as chamadas em um formato compreensível para o objeto de destino.

  2. Quando várias subclasses existentes devem ter funcionalidades comuns. Em vez de subclasses adicionais (sua criação 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 os 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 e, se houver um grande número de pontos incompatíveis, seu número pode crescer para tamanhos incontroláveis.

Não confundir com Fachada e Decorador

Após um exame superficial, o Adaptador pode ser confundido com os padrões Fachada e Decorador. A diferença entre um Adaptador e uma Fachada é que uma Fachada introduz uma nova interface e envolve um subsistema inteiro. Bem, o Decorator, ao contrário do Adapter, altera o objeto em si, não a interface.

Algoritmo de implementação passo a passo

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

  2. Defina uma interface cliente em nome da qual outra classe será usada.

  3. Implemente uma classe de adaptador baseada na interface definida na etapa anterior.

  4. Na classe do adaptador, crie um campo que armazene uma referência ao objeto. Esta referência é passada no construtor.

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

    • Transfira a chamada sem modificação;

    • Altere dados, aumente/diminua o número de chamadas para o método de destino, expanda ainda mais a composição dos dados, etc.

    • Como último recurso, se um método específico for incompatível, lance uma UnsupportedOperationException, que precisa ser estritamente documentada.

  6. Se o aplicativo usar o adaptador apenas por meio da interface do cliente (como no exemplo acima), isso permitirá que os adaptadores sejam estendidos sem problemas no futuro.

É claro que um padrão de design não é uma panacéia para todos os males, mas com sua ajuda você pode resolver com elegância o problema de incompatibilidade de objetos com diferentes interfaces. Um desenvolvedor que conhece padrões básicos está vários passos acima daqueles que simplesmente sabem escrever algoritmos, porque eles são necessários para criar aplicações sérias. Reutilizar código torna-se menos difícil e manter é um prazer. Isso é tudo por hoje! Mas em breve continuaremos a conhecer diferentes padrões de design :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION