Visão Geral do Padrão Adaptador
O Padrão Adaptador é um padrão de design estrutural que facilita a colaboração entre objetos com interfaces incompatíveis. Ele encapsula a adaptação necessária, permitindo que cmoponentes distintos funcionem em conjunto sem modificar suas estruturas originais. Um exemplo prático é um leitor de cartões que atua como intermediário entre um cartão de memória e um notebook, traduzindo as requisições entre os dois dispositivos.
Este padrão é frequentemente denominado Wrapper, pois envolve a classe-alvo em uma camada adicional. Ele resolve o problema de utilizar classes existentes cujas enterfaces não se alinham com as necessidades do sistema, sem a possibilidade de alterar o código legado.
Implementação Básica do Padrão Adaptador
Considere a definição de uma interface alvo:
public interface Alvo {
void executar();
}
Uma classe concreta que implementa esta interface:
public class AlvoConcreto implements Alvo {
@Override
public void executar() {
System.out.println("Execução da classe AlvoConcreto");
}
}
A classe que precisa ser adaptada, com sua própria interface:
public interface Adaptavel {
void operacaoEspecifica();
}
A implementação concreta da classe a ser adaptada:
public class AdaptavelConcreto implements Adaptavel {
@Override
public void operacaoEspecifica() {
System.out.println("Operação específica da classe AdaptavelConcreto");
}
}
O adaptador que integra as duas interfaces:
public class Adaptador implements Alvo {
private final Adaptavel componente;
public Adaptador(Adaptavel componente) {
this.componente = componente;
}
@Override
public void executar() {
System.out.println("Delegando ao adaptador");
componente.operacaoEspecifica();
}
}
Utilização no código cliente:
public class Cliente {
public static void main(String[] args) {
Adaptavel original = new AdaptavelConcreto();
Alvo adaptado = new Adaptador(original);
adaptado.executar();
}
}
Saída esperada:
Delegando ao adaptador
Operação específica da classe AdaptavelConcreto
Considerações de Uso
- O padrão é aplicável quando interfaces existentes não podem ser alteradas, sendo uma solução de refatoração em sistemas em operação.
- Em linguagens com herança única, como Java, a adaptação por herança pode limitar a flexibilidade, exigindo que a classe-alvo seja uma interface.
- Métodos da classe adaptada podem ser expostos, aumentando a complexidade de uso.
- O uso excessivo pode tornar o sistema mais difícil de manter, sendo crucial uma análise de custo-benefício.
- A conversão adequada de parâmetros e o direcionamento de chamadas são essenciais para a robustez do adaptador.
Aplicações Práticas
- Integração de bibliotecas de terceiros com interfaces incompatíveis, evitando modiifcações no código externo.
- Desenvolvimento de módulos genéricos que processam diversas fontes de dados, como bancos de dados ou arquivos, através de adaptadores específicos.
- Unificação de interfaces de saída em sistemas com tipos de entrada variáveis, garantindo uma API consistente.
- Inserção de uma camada de abstração para controlar o acesso a classes complexas, simplificando a interação do cliente.
Variantes de Implementação
Adaptação por Herança
Esta abordagem utiliza herança para sobrescrever métodos, adequada para cenários onde a classe base pode ser modificada:
public class AdaptavelBase {
public void operacaoEspecifica() {
System.out.println("Método da classe base");
}
}
public class AdaptadorHeranca extends AdaptavelBase {
@Override
public void executar() {
operacaoEspecifica();
}
}
Adaptação por Composição
Baseada em composição, promove maior flexibilidade e desacoplamento:
public interface Destino {
void acao();
}
public class Fonte {
public void acaoOriginal() {
System.out.println("Ação da fonte");
}
}
public class AdaptadorComposicao implements Destino {
private final Fonte fonte;
public AdaptadorComposicao(Fonte fonte) {
this.fonte = fonte;
}
@Override
public void acao() {
fonte.acaoOriginal();
}
}
Adaptação com Decorador
Combina adaptação e funcionalidades adicionais, estendendo o comportamento da classe adaptada:
public interface Componente {
void processar();
}
public class ComponenteConcreto implements Componente {
@Override
public void processar() {
System.out.println("Processamento padrão");
}
}
public class AdaptadorDecorador implements Componente {
private final Componente componente;
public AdaptadorDecorador(Componente componente) {
this.componente = componente;
}
@Override
public void processar() {
System.out.println("Pré-processamento");
componente.processar();
System.out.println("Pós-processamento");
}
}
Adaptação via Proxy
Atua como intermediário para controle de acesso, mantendo a mesma interface:
public interface Servico {
void executarServico();
}
public class ServicoReal implements Servico {
@Override
public void executarServico() {
System.out.println("Executando serviço real");
}
}
public class AdaptadorProxy implements Servico {
private final Servico servico;
public AdaptadorProxy(Servico servico) {
this.servico = servico;
}
@Override
public void executarServico() {
System.out.println("Verificando permissões");
servico.executarServico();
}
}