Ao agrupar uma biblioteca de terceiros, você adiciona uma camada adicional de abstração sobre ela. Isso tem algumas vantagens:
Sua base de código se torna mais flexível a alterações
Se você precisar substituir a biblioteca por outra, basta alterar sua implementação no wrapper - em um só lugar . Você pode alterar a implementação do wrapper e não precisa mudar nada sobre qualquer outra coisa; em outras palavras, você tem um sistema fracamente acoplado. Caso contrário, você teria que passar por toda a sua base de código e fazer modificações em todos os lugares - o que obviamente não é o que você deseja.
Você pode definir a API do wrapper independentemente da API da biblioteca
Bibliotecas diferentes podem ter APIs muito diferentes e, ao mesmo tempo, nenhuma delas pode ser exatamente o que você precisa. E se alguma biblioteca precisar que um token seja passado junto com todas as chamadas? Você pode transmitir o token em seu aplicativo sempre que precisar usar a biblioteca ou pode protegê-lo em algum lugar mais central, mas, em qualquer caso, você precisa do token. Sua classe de wrapper simplifica tudo isso novamente - porque você pode manter o token dentro da sua classe de wrapper, nunca o expõe a nenhum componente do aplicativo e abstrai completamente a necessidade. Uma enorme vantagem se você já usou uma biblioteca que não enfatiza o bom design da API.
O teste de unidade é muito mais simples
Os testes de unidade devem testar apenas uma coisa. Se você deseja testar uma unidade, precisa zombar de suas dependências. Isso se torna ainda mais importante se essa classe fizer chamadas de rede ou acessar algum outro recurso fora do seu software. Ao agrupar a biblioteca de terceiros, é fácil zombar dessas chamadas e retornar dados de teste ou o que o teste de unidade exigir. Se você não tem essa camada de abstração, fica muito mais difícil fazer isso - e na maioria das vezes isso resulta em muito código feio.
Você cria um sistema fracamente acoplado
Alterações no seu wrapper não afetam outras partes do seu software - pelo menos desde que você não altere o comportamento do seu wrapper. Ao introduzir uma camada de abstração como esse wrapper, você pode simplificar as chamadas para a biblioteca e remover quase completamente a dependência do seu aplicativo nessa biblioteca. Seu software usará apenas o wrapper e não fará diferença a maneira como o wrapper é implementado ou como ele faz o que faz.
Exemplo Prático
Sejamos honestos. As pessoas podem discutir sobre as vantagens e desvantagens de algo assim por horas - e é por isso que prefiro apenas mostrar um exemplo.
Digamos que você tenha algum tipo de aplicativo Android e precise baixar imagens. Existem várias bibliotecas por aí que facilitam o carregamento e o armazenamento de imagens em cache, por exemplo, o Picasso ou o Universal Image Loader .
Agora podemos definir uma interface que vamos usar para agrupar qualquer biblioteca que acabarmos usando:
public interface ImageService {
Bitmap load(String url);
}
Essa é a interface que agora podemos usar em todo o aplicativo sempre que precisarmos carregar uma imagem. Podemos criar uma implementação dessa interface e usar injeção de dependência para injetar uma instância dessa implementação em todos os lugares em que usamos o ImageService
.
Digamos que inicialmente decidimos usar o Picasso. Agora podemos escrever uma implementação para a ImageService
qual usa o Picasso internamente:
public class PicassoImageService implements ImageService {
private final Context mContext;
public PicassoImageService(Context context) {
mContext = context;
}
@Override
public Bitmap load(String url) {
return Picasso.with(mContext).load(url).get();
}
}
Bem direto, se você me perguntar. O empacotador em torno das bibliotecas não precisa ser complicado para ser útil. A interface e a implementação têm menos de 25 linhas de código combinadas; portanto, não houve nenhum esforço para criar isso, mas já ganhamos algo fazendo isso. Veja o Context
campo na implementação? A estrutura de injeção de dependência de sua escolha já cuidará de injetar essa dependência antes de usarmos o nosso ImageService
, seu aplicativo agora não precisa se preocupar com a forma como as imagens são baixadas e com as dependências que a biblioteca possa ter. Tudo o que seu aplicativo vê é um ImageService
e, quando precisa de uma imagem, chama load()
com um URL - simples e direto.
No entanto, o benefício real ocorre quando começamos a mudar as coisas. Imagine que agora precisamos substituir o Picasso pelo Universal Image Loader porque o Picasso não suporta alguns recursos que precisamos absolutamente no momento. Agora temos que vasculhar nossa base de código e substituir tediosamente todas as chamadas para o Picasso e depois lidar com dezenas de erros de compilação porque esquecemos de algumas chamadas do Picasso? Não. Tudo o que precisamos fazer é criar uma nova implementação ImageService
e dizer à nossa estrutura de injeção de dependência para usar essa implementação a partir de agora:
public class UniversalImageLoaderImageService implements ImageService {
private final ImageLoader mImageLoader;
public UniversalImageLoaderImageService(Context context) {
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.defaultDisplayImageOptions(defaultOptions)
.build();
mImageLoader = ImageLoader.getInstance();
mImageLoader.init(config);
}
@Override
public Bitmap load(String url) {
return mImageLoader.loadImageSync(url);
}
}
Como você pode ver, a implementação pode ser muito diferente, mas isso não importa. Não tivemos que alterar uma única linha de código em nenhum outro lugar do nosso aplicativo. Usamos uma biblioteca completamente diferente, que pode ter recursos completamente diferentes ou pode ser usada de maneira muito diferente, mas nosso aplicativo simplesmente não se importa. O mesmo de antes do resto do nosso aplicativo, apenas vê a ImageService
interface com seu load()
método e, no entanto, esse método é implementado não importa mais.
Pelo menos para mim tudo isso já parece muito bom, mas espere! Ainda tem mais. Imagine que você está escrevendo testes de unidade para uma classe em que está trabalhando e essa classe usa o ImageService
. É claro que você não pode permitir que seus testes de unidade façam chamadas de rede para algum recurso localizado em outro servidor, mas como você está usando agora, ImageService
você pode facilmente load()
retornar uma estática Bitmap
usada para os testes de unidade implementando uma simulação ImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
Para resumir, agrupando bibliotecas de terceiros, sua base de código se torna mais flexível a alterações, em geral mais simples, mais fácil de testar e você reduz o acoplamento de diferentes componentes em seu software - tudo que se torna cada vez mais importante quanto mais tempo você mantém um software.