Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
O design pattern Adapter, é um padrão do tipo estrutural, a solução que ele nos provê é bem interessante, poder utilizar uma classe como uma interface na qual essa classe não implementa, esse tipo de abordagem é bem útil em evoluções ou adaptações que são necessárias no sistema.
Esse pattern vai atuar como um intermediador entre duas interfaces que são incompatíveis, fazendo com que o utilizador dessas interfaces consiga utilizar uma classe que não foi projetada originalmente para aquela interface.
Esse tipo de abordagem traz uma grande vantagem quando estamos trabalhando em melhorias no sistema e precisamos adicionar novas funcionalidades ou trocar componentes, com um sistema que já está em operação, pois podemos ir trocando de forma gradual sem afetar de fato o funcionamento do sistema como um todo.
Para exemplificar como seria esse implementação, vamos utilizar um exemplo de queries em banco de dados, sabemos que o mais comum é utilizar um ORM que fará o trabalho pesado para nós, mas essa é uma boa maneira de exemplificar esse tipo de implementação.
Como primeiro passo vamos imaginar que temos uma interface que define o método na qual utilizamos para chamar nossa query.
public interface QueryBancoDados {
Object executarQuery();
}
QueryBancoDados.javaE que o nosso sistema já utiliza um banco de dados relacional, e para isso temos uma classe concreta que implementa essa interface fazendo a chamada ao banco.
public class SQLQuery implements QueryBancoDados {
@Override
public Object executarQuery() {
System.out.println("SELECT * FROM pessoas");
return null;
}
}
SQLQuery.javaNão vamos fazer a implementação detalhada de como seria essa chamada e ler os resultados e transformar em objetos para não fugir do nosso foco que é a implementação do adapter em si.
Vamos imaginar que nosso sistema tem um controlador que utiliza-se dessa implementação para buscar as pessoas no banco de dados e exibir.
public class PessoasController {
public Object buscarPessoas() {
QueryBancoDados query = new SQLQuery();
return query.executarQuery();
}
}
PessoaController.javaTemos um exemplo de uma implementação já funcionando, e vamos utilizar nosso pattern para atualizar essa implementação adicionando uma nova ferramenta.
Imagine que essa tabela pessoa será migrada para uma “collection” do MongoDB e que agora para consumir esse recurso vamos precisar buscar os dados de lá, como se trata de um outro banco de dados e que se comunica de uma forma diferente vamos precisar implementar uma nova forma de leitura, veja o exemplo a seguir.
public class MongoQuery {
public Object executar() {
System.out.println("db.collection('pessoas').find({})");
return null;
}
}
MongoQuery.javaObserve que nossa implementação é completamente diferente da anterior e precisamos de uma forma para substituir a chamada para esse novo banco, para isso vamos criar um Adapter.
public class MongoQueryAdapter implements QueryBancoDados {
private MongoQuery mongoQuery;
public MongoQueryAdapter(MongoQuery mongoQuery) {
super();
this.mongoQuery = mongoQuery;
}
@Override
public Object executarQuery() {
return mongoQuery.executar();
}
}
MongoQueryAdapter.javaA criação do nosso adapter é relativamente simples, precisamos apenas criar uma nova classe que encapsule nossa implementação atual, mas que implemente a interface alvo, para que no método a ser invocado, possamos substituir o comportamento chamando assim nossa nova classe que irá ler os dados do outro banco.
public class PessoasController {
public Object buscarPessoas() {
//Implementação antiga
// QueryBancoDados query = new SQLQuery();
// Nova implementação utlizando o adapter
QueryBancoDados query = new MongoQueryAdapter(new MongoQuery());
return query.executarQuery();
}
}
PessoaController.javaObserve como a substituição acaba ficando simples, claro que nesse contexto que estamos seria bem simples fazer a substituição direta, mas em sistemas mais complexos pode não ser tão fácil assim trocar componentes, então essa acaba sendo uma abordagem que resolve bastante problemas.
Como todo design pattern esse não é uma exceção e tem lá suas desvantagens que devem ser consideradas quando você escolhe utilizá-lo, vamos citar algumas a seguir.
Esse padrão por ser ótimo em fazer conversar duas assinaturas diferentes pode trazer mais complexidade para o seu código, dependendo de como está estruturado e a quantidade de adapters que você precisa criar o código pode ficar bem mais complexo de ser entendido e ainda mais difícil de manter.
Esse tipo de implementação também pode causar um overhead de desempenho, pois está acontecendo um redirecionamento de chamadas de métodos, em sistemas que o desempenho é muito considerado e o ambiente onde ele está sendo executado é limitado, a utilização desse padrão pode trazer problemas.
Essa abordagem também faz o nosso sistema ficar mais acoplado, pois utiliza diferentes partes recursos do sistema, e no futuro remover um componente pode ser algo desafiador.
Identificar a forma correta de construir o adapter nem sempre é tão simples, a depender da complexidade do sistema, pode ser muito difícil conseguir construir um adapter que não seja necessário alterar muitos pontos do sistema, isso pode trazer mais complexidade e transformar o desenho do seu sistema em algo bem confuso.
Muitas das vezes esses adapters são construídos para servir de forma temporária, para uma evolução do sistema, o problema é que às vezes o temporário acaba se tornando permanente e depois se transformando em uma legado muito difícil de se lidar e o pior de tudo muito difícil de ser removido, então tenha bastante cuidado com esses adaptadores temporários.
Como abordado aqui esse é uma excelente padrão quando utilizado da forma correta, sua utilização não serve apenas para criar código temporários em uma possível evolução do sistema, mas serve como um padrão a ser considerado para construção de interfaces desacopladas, é bastante utilizado em arquiteturas modernas como a hexagonal.
Como todo padrão tem seus prós e contras e o importante é analisar onde será implementado esse padrão e tentar prever os futuros problemas que pode ocasionar.
Importante lembrar que toda implementação sempre pode trazer problemas futuros, e sempre devemos pensar em construir um código limpo e de fácil entendimento para outros desenvolvedores, excelentes programadores sempre tentam implementar funcionalidades que seja de fácil entendimento para outros, e que outros consigam dar continuidade ao seu trabalho.