Padrões de Projetos - Adapter - Exemplo em Java
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.
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.
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
- 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.
- 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
- 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.
- 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.
- O sistema cliente fica desacoplado das classes concretas. Ele lida apenas com a interface comum (
- 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.
- 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
- 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.
- 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.
- Se houver muitas classes que não precisam de adaptação (como o
- 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.
- 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.
- 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.