Avvolgendo una libreria di terze parti aggiungi un ulteriore livello di astrazione su di essa. Questo ha alcuni vantaggi:
La tua base di codice diventa più flessibile alle modifiche
Se hai mai bisogno di sostituire la libreria con un'altra, devi solo cambiare l'implementazione nel tuo wrapper, in un unico posto . Puoi cambiare l'implementazione del wrapper e non devi cambiare nulla di tutto, in altre parole hai un sistema liberamente accoppiato. Altrimenti dovresti esaminare l'intera base di codice e apportare modifiche ovunque, il che ovviamente non è quello che desideri.
È possibile definire l'API del wrapper indipendentemente dall'API della libreria
Librerie diverse possono avere API notevolmente diverse e allo stesso tempo nessuna di esse può essere esattamente ciò di cui hai bisogno. Che cosa succede se una libreria necessita di un token per essere passato insieme ad ogni chiamata? Puoi trasferire il token nella tua app ovunque sia necessario utilizzare la libreria o puoi proteggerlo da qualche parte più centralmente, ma in ogni caso ti serve il token. La tua classe wrapper semplifica di nuovo tutto questo, perché puoi semplicemente mantenere il token all'interno della tua classe wrapper, non esponendolo mai a nessun componente all'interno della tua app e togliendo completamente la necessità. Un enorme vantaggio se hai mai usato una libreria che non enfatizza una buona progettazione delle API.
Il test unitario è molto più semplice
I test unitari dovrebbero testare solo una cosa. Se vuoi testare un'unità di una classe devi prendere in giro le sue dipendenze. Questo diventa ancora più importante se quella classe effettua chiamate di rete o accede ad altre risorse al di fuori del tuo software. Avvolgendo la libreria di terze parti è facile prendere in giro quelle chiamate e restituire i dati di test o qualsiasi altra cosa richiesta da unit test. Se non hai un tale livello di astrazione, diventa molto più difficile farlo - e il più delle volte si traduce in un sacco di codice brutto.
Si crea un sistema liberamente accoppiato
Le modifiche al wrapper non hanno alcun effetto su altre parti del software, almeno finché non si modifica il comportamento del wrapper. Introducendo uno strato di astrazione come questo wrapper puoi semplificare le chiamate alla libreria e rimuovere quasi completamente la dipendenza dell'app dalla libreria. Il tuo software utilizzerà semplicemente il wrapper e non farà alcuna differenza nel modo in cui il wrapper viene implementato o come fa ciò che fa.
Esempio pratico
Diciamo la verità. Le persone possono discutere dei vantaggi e degli svantaggi di qualcosa del genere per ore, motivo per cui preferisco piuttosto mostrarvi un esempio.
Supponiamo che tu abbia una sorta di app Android e devi scaricare immagini. Ci sono un sacco di librerie là fuori che rendono il caricamento e la memorizzazione nella cache delle immagini un gioco da ragazzi, ad esempio Picasso o Universal Image Loader .
Ora possiamo definire un'interfaccia che useremo per avvolgere qualunque libreria finiremo usando:
public interface ImageService {
Bitmap load(String url);
}
Questa è l'interfaccia che ora possiamo usare in tutta l'app ogni volta che dobbiamo caricare un'immagine. Siamo in grado di creare un'implementazione di questa interfaccia e utilizzare l'iniezione di dipendenza per iniettare un'istanza di tale implementazione ovunque utilizziamo ImageService
.
Diciamo che inizialmente decidiamo di usare Picasso. Ora possiamo scrivere un'implementazione per la ImageService
quale utilizza 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();
}
}
Abbastanza semplice se me lo chiedi. Avvolgere le biblioteche non deve essere complicato per essere utile. L'interfaccia e l'implementazione hanno meno di 25 righe di codice combinate, quindi non è stato quasi uno sforzo creare questo, ma già otteniamo qualcosa facendo questo. Vedi il Context
campo nell'implementazione? Il framework di iniezione delle dipendenze di tua scelta si occuperà già di iniettare quella dipendenza prima di usare la nostra ImageService
, la tua app ora non deve preoccuparsi di come le immagini vengono scaricate e di quali dipendenze possa avere la libreria. Tutto ciò che la tua app vede è un ImageService
e quando ha bisogno di un'immagine con cui chiama load()
con un url - semplice e diretto.
Tuttavia, il vero vantaggio arriva quando iniziamo a cambiare le cose. Immagina che ora dobbiamo sostituire Picasso con Universal Image Loader perché Picasso non supporta alcune funzionalità di cui abbiamo assolutamente bisogno in questo momento. Dobbiamo ora esaminare la nostra base di codice e sostituire noiosamente tutte le chiamate a Picasso e quindi gestire dozzine di errori di compilazione perché abbiamo dimenticato alcune chiamate di Picasso? No. Tutto ciò che dobbiamo fare è creare una nuova implementazione ImageService
e dire al nostro framework di iniezione delle dipendenze di utilizzare questa implementazione da ora in poi:
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);
}
}
Come puoi vedere l'implementazione potrebbe essere molto diversa, ma non importa. Non abbiamo dovuto modificare una singola riga di codice in nessun altro punto della nostra app. Usiamo una libreria completamente diversa che potrebbe avere funzionalità completamente diverse o potrebbe essere utilizzata in modo molto diverso, ma alla nostra app non importa. Come prima il resto della nostra app vede l' ImageService
interfaccia con il suo load()
metodo e tuttavia questo metodo è implementato non ha più importanza.
Almeno per me questo già suona già abbastanza carino, ma aspetta! C'è ancora di più. Immagina di scrivere unit test per una classe su cui stai lavorando e questa classe usa il ImageService
. Ovviamente non puoi permettere alle tue unit test di effettuare chiamate di rete verso alcune risorse situate su qualche altro server ma dato che ora stai usando ImageService
puoi facilmente load()
restituire uno statico Bitmap
usato per le unit test implementando un derisione ImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
Per riassumere avvolgendo le librerie di terze parti, la tua base di codice diventa più flessibile alle modifiche, nel complesso più semplice, più facile da testare e riduci l'accoppiamento di diversi componenti nel tuo software - tutte cose che diventano sempre più importanti man mano che mantieni un software.