Indem Sie eine Bibliothek eines Drittanbieters einschließen, fügen Sie eine zusätzliche Abstraktionsebene hinzu. Dies hat einige Vorteile:
Ihre Codebasis wird flexibler für Änderungen
Wenn Sie die Bibliothek jemals durch eine andere ersetzen müssen, müssen Sie nur Ihre Implementierung in Ihrem Wrapper ändern - an einem Ort . Sie können die Implementierung des Wrappers ändern und müssen an nichts anderem etwas ändern, mit anderen Worten, Sie haben ein lose gekoppeltes System. Andernfalls müssten Sie Ihre gesamte Codebasis durchgehen und überall Änderungen vornehmen - was offensichtlich nicht das ist, was Sie wollen.
Sie können die API des Wrappers unabhängig von der API der Bibliothek definieren
Verschiedene Bibliotheken können sehr unterschiedliche APIs haben, und gleichzeitig ist keine davon genau das, was Sie benötigen. Was ist, wenn in einer Bibliothek bei jedem Aufruf ein Token übergeben werden muss? Sie können das Token in Ihrer App überall dort weitergeben, wo Sie die Bibliothek verwenden möchten, oder Sie können es an einem zentraleren Ort speichern, aber auf jeden Fall benötigen Sie das Token. Mit Ihrer Wrapper-Klasse ist das wieder ganz einfach: Sie können das Token einfach in Ihrer Wrapper-Klasse belassen, es niemals einer Komponente in Ihrer App aussetzen und die Notwendigkeit dafür vollständig ausschließen. Ein großer Vorteil, wenn Sie jemals eine Bibliothek verwendet haben, die kein gutes API-Design hervorhebt.
Unit-Tests sind viel einfacher
Unit-Tests sollten nur eines testen. Wenn Sie eine Klasse einem Komponententest unterziehen möchten, müssen Sie sich über ihre Abhängigkeiten lustig machen. Dies wird noch wichtiger, wenn diese Klasse Netzwerkanrufe durchführt oder auf eine andere Ressource außerhalb Ihrer Software zugreift. Indem Sie die Bibliothek eines Drittanbieters einbinden, können Sie diese Aufrufe einfach verspotten und Testdaten oder was auch immer für diesen Komponententest erforderlich ist, zurückgeben. Wenn Sie nicht über eine solche Abstraktionsebene verfügen, wird dies sehr viel schwieriger - und die meiste Zeit führt dies zu viel hässlichem Code.
Sie erstellen ein lose gekoppeltes System
Änderungen an Ihrem Wrapper haben keine Auswirkungen auf andere Teile Ihrer Software - zumindest solange Sie das Verhalten Ihres Wrappers nicht ändern. Durch die Einführung einer Abstraktionsebene wie dieser Wrapper können Sie die Aufrufe der Bibliothek vereinfachen und die Abhängigkeit Ihrer App von dieser Bibliothek fast vollständig entfernen. Ihre Software verwendet nur den Wrapper und es macht keinen Unterschied, wie der Wrapper implementiert ist oder was er tut.
Praktisches Beispiel
Lass uns ehrlich sein. Die Leute können stundenlang über die Vor- und Nachteile von so etwas streiten - deshalb zeige ich Ihnen lieber nur ein Beispiel.
Angenommen, Sie haben eine Art Android-App und müssen Bilder herunterladen. Es gibt eine Reihe von Bibliotheken, die das Laden und Zwischenspeichern von Bildern zum Kinderspiel machen, zum Beispiel Picasso oder den Universal Image Loader .
Wir können jetzt eine Schnittstelle definieren, die wir verwenden, um die Bibliothek zu verpacken, die wir letztendlich verwenden:
public interface ImageService {
Bitmap load(String url);
}
Dies ist die Benutzeroberfläche, die wir jetzt in der gesamten App verwenden können, wenn wir ein Bild laden müssen. Wir können eine Implementierung dieser Schnittstelle erstellen und mithilfe der Abhängigkeitsinjektion eine Instanz dieser Implementierung überall dort einfügen, wo wir die verwenden ImageService
.
Nehmen wir an, wir entscheiden uns zunächst für Picasso. Wir können jetzt eine Implementierung schreiben, für ImageService
die Picasso intern verwendet wird:
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();
}
}
Ziemlich direkt, wenn du mich fragst. Wrapper um Bibliotheken müssen nicht kompliziert sein, um nützlich zu sein. Die Schnittstelle und die Implementierung haben weniger als 25 kombinierte Codezeilen, so dass es kaum Aufwand gab, dies zu erstellen, aber wir gewinnen bereits etwas, indem wir dies tun. Siehe das Context
Feld in der Implementierung? Das Abhängigkeitsinjektionsframework Ihrer Wahl wird sich bereits darum kümmern, diese Abhängigkeit zu injizieren, bevor wir unsere verwenden ImageService
. Ihre App muss sich jetzt nicht mehr darum kümmern, wie die Bilder heruntergeladen werden und welche Abhängigkeiten diese Bibliothek möglicherweise hat. Alles, was Ihre App sieht, ist ein ImageService
und wenn sie ein Bild benötigt, ruft sie es load()
mit einer URL auf - einfach und unkompliziert.
Der eigentliche Vorteil ergibt sich jedoch, wenn wir anfangen, Dinge zu ändern. Stellen Sie sich vor, wir müssen Picasso jetzt durch Universal Image Loader ersetzen, da Picasso einige Funktionen, die wir momentan unbedingt benötigen, nicht unterstützt. Müssen wir jetzt unsere Codebasis durchkämmen und mühsam alle Aufrufe von Picasso ersetzen und uns dann mit Dutzenden von Kompilierungsfehlern befassen, weil wir einige Picasso-Aufrufe vergessen haben? Nein. Wir müssen lediglich eine neue Implementierung von erstellen ImageService
und unserem Abhängigkeitsinjektionsframework mitteilen, dass diese Implementierung von nun an verwendet werden soll:
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);
}
}
Wie Sie sehen, kann die Implementierung sehr unterschiedlich sein, aber es spielt keine Rolle. Wir mussten an keiner anderen Stelle in unserer App eine einzige Codezeile ändern. Wir verwenden eine völlig andere Bibliothek, die möglicherweise völlig andere Funktionen aufweist oder sehr unterschiedlich verwendet wird, aber unsere App kümmert sich einfach nicht darum. Wie zuvor sieht der Rest unserer App nur das ImageService
Interface mit seiner load()
Methode und diese Methode ist jedoch nicht mehr von Bedeutung.
Zumindest für mich klingt das alles schon ganz nett, aber warte! Es gibt noch mehr. Stellen Sie sich vor, Sie schreiben Komponententests für eine Klasse, an der Sie arbeiten, und diese Klasse verwendet die ImageService
. Natürlich können Sie nicht zulassen, dass Ihre Komponententests Netzwerkanrufe an eine Ressource auf einem anderen Server senden, aber da Sie jetzt die verwenden ImageService
, können Sie load()
eine Bitmap
für die Komponententests verwendete statische Aufladung einfach zurückgeben lassen , indem Sie eine verspottete Anweisung implementieren ImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
Zusammenfassend lässt sich festhalten, dass Ihre Codebasis durch das Einschließen von Bibliotheken von Drittanbietern flexibler für Änderungen wird, insgesamt einfacher und einfacher zu testen ist und Sie die Kopplung verschiedener Komponenten in Ihrer Software verringern - alles Dinge, die immer wichtiger werden, je länger Sie eine Software warten.