Padrões de Projetos - Adapter - Exemplo em Java

De Aulas



Afluentes: Modelos, métodos e técnicas da engenharia de software

Descrição

No contexto dos padrões de projetos (Design Patterns), o Adapter é um padrão estrutural que permite que interfaces incompatíveis possam trabalhar juntas. Ele age como um "adaptador", convertendo a interface de uma classe em outra que o cliente espera. Existem duas variações principais: Object Adapter (usa composição) e Class Adapter (usa herança).

Problema

Imagine que você está criando uma aplicação de monitoramento do mercado de ações da bolsa. A aplicação baixa os dados as ações de múltiplas fontes em formato XML e então mostra gráficos e diagramas maneiros para o usuário.

Em algum ponto, você decide melhorar a aplicação ao integrar uma biblioteca de análise de terceiros. Mas aqui está a pegadinha: a biblioteca só trabalha com dados em formato JSON.

Gof exemplo adapter 00.png

Você poderia mudar a biblioteca para que ela funcione com XML. Contudo, isso pode quebrar algum código existente que depende da biblioteca. E pior, você pode não ter acesso ao código fonte da biblioteca para começo de conversa, fazendo dessa abordagem uma tarefa impossível. Você pode criar um adaptador. Ele é um objeto especial que converte a interface de um objeto para que outro objeto possa entendê-lo.

Gof exemplo adapter 01.png

Fonte: https://refactoring.guru/pt-br/design-patterns/adapter

Class Adapter

A Class Adapter é o adaptador que herda tanto a interface esperada pelo cliente quanto a classe que precisa ser adaptada. Esse padrão é mais comum em linguagens que suportam herança múltipla, o que o torna menos comum em Java, já que Java não suporta herança múltipla de classes, apenas de interfaces. No entanto, em Java, o Class Adapter pode ser implementado usando herança simples e adaptando classes que têm funcionalidades compatíveis.

Exemplo em Java

FileProcessor

A Interface FileProcessor define um método genérico processFile() que o sistema espera para processar diferentes tipos de arquivos (JSON e XML).

interface FileProcessor {
    void processFile(String fileName);
}

JsonProcessor

A classe JsonProcessor implementa diretamente a interface FileProcessor, não sendo necessário criar um adaptador. Ela possui o método processFile(), que processa arquivos JSON.

class JsonProcessor implements FileProcessor {
    @Override
    public void processFile(String fileName) {
        System.out.println("Processing JSON file: " + fileName);
    }
}

XmlProcessor

Aqui temos o XmlProcessor e vejam que ele não é compatível com a classe FileProcessor, ou seja, a classe XmlProcessor não implementa a interface FileProcessor. Ela usa o método processXml(), que não se alinha com o que o sistema espera.

class XmlProcessor {
    public void processXml(String fileName) {
        System.out.println("Processing XML file: " + fileName);
    }
}

XmlAdapter

A classe XmlAdapter implementa a interface FileProcessor e adapta a classe XmlProcessor para essa interface. Ela encapsula um objeto XmlProcessor e delega a ele a chamada para o método correto (processXml()), permitindo que a classe seja usada no lugar de um FileProcessor.

class XmlAdapter implements FileProcessor {
    private XmlProcessor xmlProcessor;

    public XmlAdapter(XmlProcessor xmlProcessor) {
        this.xmlProcessor = xmlProcessor;
    }

    @Override
    public void processFile(String fileName) {
        xmlProcessor.processXml(fileName);
    }
}

Programa Principal (Main)

O cliente, no caso nosso programa principal Main, usa um FileProcessor para processar qualquer tipo de arquivo. No caso de arquivos XML, ele utiliza o adaptador (XmlAdapter), e para arquivos JSON, ele usa diretamente o processador JSON.

public class Main {
    public static void main(String[] args) {
        // Agora processamos nossos arquivos.

        // Processador de JSON (não precisa de adaptador)
        FileProcessor jsonProcessor = new JsonProcessor();
        jsonProcessor.processFile("data.json");
        
        // Processador de XML usando o adaptador
        FileProcessor xmlProcessor = new XmlAdapter(new XmlProcessor());
        xmlProcessor.processFile("data.xml");
    }
}

Vantagens do Padrão Adapter

  1. Reuso de código existente:
    • O Adapter permite que você reutilize código legado ou de terceiros, que talvez não esteja alinhado com a interface que o sistema espera, sem precisar modificar esse código. No exemplo, a classe XmlProcessor foi reutilizada sem alterar sua implementação.
  2. Facilita a integração de sistemas heterogêneos:
    • O Adapter facilita a integração entre sistemas que foram projetados de forma independente e possuem interfaces incompatíveis. No caso de processadores diferentes (JSON e XML), ambos podem ser usados pelo sistema sem grandes mudanças.
  3. Desacoplamento de dependências:
    • O sistema cliente fica desacoplado das classes concretas. Ele lida apenas com a interface comum (FileProcessor no exemplo). Se você precisar adicionar suporte a novos tipos de arquivos, como CSV ou YAML, pode criar novos adaptadores sem mudar o código existente do sistema.
  4. Fácil manutenção e extensão:
    • Ao introduzir novos formatos de arquivo ou novos comportamentos, você pode adicionar mais adaptadores de maneira isolada, sem impactar o restante do sistema. A estrutura é modular e extensível.
  5. Padrão bem compreendido e amplamente utilizado:
    • O Adapter é um padrão de projeto bem estabelecido, o que significa que outros desenvolvedores familiarizados com design patterns entenderão rapidamente sua função, facilitando o trabalho em equipe.

Desvantagens do Padrão Adapter

  1. Aumento da complexidade:
    • A introdução de adaptadores pode aumentar a complexidade do código, especialmente se o número de classes que precisam de adaptação for grande. Isso pode tornar o código mais difícil de entender e manter, especialmente em projetos muito grandes.
  2. Overhead desnecessário em alguns casos:
    • Se houver muitas classes que não precisam de adaptação (como o JsonProcessor no nosso exemplo), o uso de adaptadores pode parecer redundante. O adaptador pode introduzir uma camada adicional desnecessária, adicionando complexidade sem um ganho real.
  3. Solução paliativa em vez de refatoração:
    • O Adapter é uma solução para compatibilizar interfaces, mas pode ser uma abordagem temporária. Dependendo da situação, refatorar o código para que as classes sejam diretamente compatíveis pode ser uma solução mais sustentável a longo prazo, eliminando a necessidade de adaptadores.
  4. Difícil de testar:
    • Como o adaptador encapsula outra classe, isso pode tornar os testes unitários mais complexos, já que você terá que testar tanto a funcionalidade da classe original quanto a do adaptador.
  5. Pode mascarar problemas de design:
    • O uso excessivo de adaptadores pode mascarar um problema maior de design, sugerindo que o sistema pode estar mal estruturado ou com uma arquitetura inadequada. Se houver muitos adaptadores, isso pode indicar a necessidade de uma refatoração maior para padronizar as interfaces.

Resumindo

  • Vantagens: Reuso de código legado, desacoplamento, extensibilidade, integração fácil e modularidade.
  • Desvantagens: Aumento de complexidade, sobrecarga desnecessária, possível solução temporária, maior dificuldade de testes e, em alguns casos, pode ocultar problemas arquitetônicos.

O Adapter é uma solução excelente quando você precisa adaptar sistemas com interfaces incompatíveis de forma rápida e eficiente, mas deve ser usado com parcimônia para evitar a introdução de complexidade desnecessária.