En encapsulant une bibliothèque tierce, vous ajoutez une couche supplémentaire d’abstraction. Cela présente quelques avantages:
Votre base de code devient plus flexible aux changements
Si vous devez remplacer la bibliothèque par une autre, il vous suffit de modifier votre implémentation dans votre wrapper - à un endroit . Vous pouvez modifier l'implémentation du wrapper sans rien changer d'autre, autrement dit, vous avez un système faiblement couplé. Sinon, vous devrez parcourir tout votre code et apporter des modifications partout - ce qui n'est évidemment pas ce que vous voulez.
Vous pouvez définir l'API du wrapper indépendamment de l'API de la bibliothèque.
Différentes bibliothèques peuvent avoir des API très différentes et en même temps aucune d’entre elles ne correspond exactement à vos besoins. Que se passe-t-il si une bibliothèque a besoin d'un jeton à transmettre avec chaque appel? Vous pouvez faire passer le jeton dans votre application partout où vous avez besoin d'utiliser la bibliothèque ou la sécuriser quelque part de manière plus centralisée, mais dans tous les cas, vous avez besoin du jeton. Votre classe de wrapper simplifie à nouveau tout cela, car vous pouvez simplement conserver le jeton dans votre classe de wrapper, ne jamais l'exposer à aucun composant de votre application et en supprimer complètement la nécessité. Un énorme avantage si vous avez déjà utilisé une bibliothèque qui ne met pas l’accent sur une bonne conception d’API.
Le test unitaire est beaucoup plus simple
Les tests unitaires ne devraient tester qu'une seule chose. Si vous voulez tester une classe, vous devez vous moquer de ses dépendances. Cela devient encore plus important si cette classe passe des appels réseau ou accède à une autre ressource en dehors de votre logiciel. En encapsulant la bibliothèque tierce, il est facile de simuler ces appels et de renvoyer des données de test ou tout autre élément requis par le test unitaire. Si vous n’avez pas une telle couche d’abstraction, il devient beaucoup plus difficile de le faire - et la plupart du temps, cela produit beaucoup de code laid.
Vous créez un système faiblement couplé
Les modifications apportées à votre enveloppe n'ont aucun effet sur les autres parties de votre logiciel, du moins tant que vous ne modifiez pas le comportement de votre enveloppe. En introduisant une couche d'abstraction comme celle-ci, vous pouvez simplifier les appels vers la bibliothèque et supprimer presque complètement la dépendance de votre application à cette bibliothèque. Votre logiciel utilisera simplement l'encapsuleur et cela ne changera rien à la façon dont l'encapsuleur est implémenté ou comment il fait ce qu'il fait.
Exemple pratique
Soyons honnêtes. Les gens peuvent discuter pendant des heures des avantages et des inconvénients d’une chose de la sorte - c’est pourquoi je préfère vous montrer un exemple.
Disons que vous avez une sorte d'application Android et que vous devez télécharger des images. Il existe de nombreuses bibliothèques qui facilitent grandement le chargement et la mise en cache des images, par exemple Picasso ou Universal Image Loader .
Nous pouvons maintenant définir une interface que nous allons utiliser pour envelopper quelle que soit la bibliothèque utilisée:
public interface ImageService {
Bitmap load(String url);
}
C'est l'interface que nous pouvons maintenant utiliser dans l'application chaque fois que nous devons charger une image. Nous pouvons créer une implémentation de cette interface et utiliser l’injection de dépendance pour injecter une instance de cette implémentation partout où nous utilisons le ImageService
.
Disons que nous avons initialement décidé d'utiliser Picasso. Nous pouvons maintenant écrire une implémentation pour ImageService
laquelle Picasso utilise en interne:
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();
}
}
Assez simple si vous me demandez. Wrapper autour des bibliothèques n'a pas besoin d'être compliqué pour être utile. L’interface et l’implémentation ont moins de 25 lignes de code combinées, ce qui a été un effort de création, mais nous gagnons déjà quelque chose en le faisant. Voir le Context
champ dans la mise en œuvre? Le framework d'injection de dépendance de votre choix se chargera déjà d'injecter cette dépendance avant même que nous utilisions notre ImageService
, votre application n'a plus à se soucier de la façon dont les images sont téléchargées ni des dépendances éventuelles de la bibliothèque. Tout ce que votre application voit est un ImageService
et quand il a besoin d'une image, il appelle load()
avec une URL - simple et direct.
Cependant, le véritable avantage vient lorsque nous commençons à changer les choses. Imaginez que nous devions maintenant remplacer Picasso par Universal Image Loader, car Picasso ne prend pas en charge certaines fonctionnalités dont nous avons absolument besoin. Devons-nous maintenant passer au crible notre base de code, et remplacer fastidieusement tous les appels de Picasso, puis traiter des dizaines d’erreurs de compilation, car nous avons oublié quelques appels à Picasso? Non. Tout ce que nous avons à faire est de créer une nouvelle implémentation ImageService
et d'indiquer à notre infrastructure d'injection de dépendance d'utiliser cette implémentation à partir de maintenant:
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);
}
}
Comme vous pouvez le constater, la mise en œuvre peut être très différente, mais cela n’a aucune importance. Nous n'avons pas eu à modifier une seule ligne de code ailleurs dans notre application. Nous utilisons une bibliothèque complètement différente qui peut avoir des fonctionnalités complètement différentes ou peut être utilisée de manière très différente, mais notre application ne s'en soucie tout simplement pas. Comme auparavant, le reste de notre application ne voit que l' ImageService
interface avec sa load()
méthode. Cependant, cette méthode n'est pas utilisée.
Au moins pour moi tout cela sonne déjà déjà bien, mais attendez! Il y a encore plus. Imaginez que vous écrivez des tests unitaires pour une classe sur laquelle vous travaillez et que cette classe utilise le ImageService
. Bien sûr, vous ne pouvez pas laisser vos tests unitaires faire des appels réseau vers une ressource située sur un autre serveur, mais puisque vous utilisez maintenant le, ImageService
vous pouvez facilement laisser load()
renvoyer une statique Bitmap
utilisée pour les tests unitaires en implémentant un modèle fictif ImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
Pour résumer en encapsulant des bibliothèques tierces, votre base de code devient plus souple aux changements, plus simple, plus facile à tester et vous réduisez le couplage des différents composants de votre logiciel - autant de choses qui deviennent de plus en plus importantes plus la maintenance d'un logiciel est longue.