Обгортаючи сторонні бібліотеки, ви додаєте додатковий шар абстракції поверх неї. У цього є кілька переваг:
Ваша кодова база стає гнучкішою до змін
Якщо вам коли-небудь потрібно замінити бібліотеку на іншу, вам потрібно змінити свою реалізацію в обгортці - в одному місці . Ви можете змінити реалізацію обгортки і не потрібно нічого міняти ні про що інше, іншими словами, у вас нещільно пов'язана система. Інакше вам доведеться пройти всю вашу кодову базу і вносити зміни всюди - що, очевидно, не те, що ви хочете.
Ви можете визначити API обгортки незалежно від API бібліотеки
Різні бібліотеки можуть мати дуже різні API, і в той же час жодна з них не може бути саме тим, що вам потрібно. Що робити, якщо для кожної бібліотеки потрібен маркер, який потрібно передавати разом із кожним викликом? Ви можете передати маркер у вашому додатку, де вам потрібно використовувати бібліотеку, або зберегти його десь центральніше, але в будь-якому випадку маркер вам потрібен. Ваш клас обгортки знову робить це все простим - адже ви можете просто тримати маркер всередині свого класу обгортки, ніколи не піддаючи його жодному компоненту всередині вашого додатка і повністю абстрагуючи необхідність у ньому. Величезна перевага, якщо ви коли-небудь використовували бібліотеку, яка не підкреслює хороший дизайн API.
Тестування одиниць простіше
Одиничні тести повинні перевірити лише одне. Якщо ви хочете перевірити клас класу, вам доведеться знущатися над його залежностями. Це стає ще важливішим, якщо цей клас здійснює мережеві дзвінки або отримує доступ до якогось іншого ресурсу поза вашим програмним забезпеченням. Обгортаючи бібліотеку сторонніх організацій, легко знущатися над цими дзвінками та повертати тестові дані або те, що вимагає тестовий пристрій. Якщо у вас немає такого шару абстракції, зробити це стає набагато складніше - і більшість випадків це призводить до великої кількості потворних кодів.
Ви створюєте нещільно пов'язану систему
Зміни вашої обгортки не впливають на інші частини вашого програмного забезпечення - принаймні до тих пір, поки ви не зміните поведінку своєї обгортки. Вводячи такий шар абстракції, як ця обгортка, ви можете спростити виклики до бібліотеки та майже повністю усунути залежність вашого додатка від цієї бібліотеки. Ваше програмне забезпечення буде просто використовувати обгортку, і це не вплине на те, як реалізується обгортка або як вона робить те, що робить.
Практичний приклад
Давайте будемо чесними. Люди можуть годинами сперечатися про переваги та недоліки чогось подібного - саме тому я набагато просто показую вам приклад.
Скажімо, у вас є якась програма для Android, і вам потрібно завантажити зображення. Існує купа бібліотек, які роблять завантаження та кешування зображень вітерцем, наприклад, Пікассо або Універсальний завантажувач зображень .
Тепер ми можемо визначити інтерфейс, який ми будемо використовувати, щоб обернути будь-яку бібліотеку, в якій ми знаходимося:
public interface ImageService {
Bitmap load(String url);
}
Це інтерфейс, який ми зараз можемо використовувати у всьому додатку, коли нам потрібно завантажити зображення. Ми можемо створити реалізацію цього інтерфейсу і використовувати ін'єкцію залежності, щоб ввести примірник цієї реалізації скрізь, де ми використовуємо ImageService
.
Скажімо, ми спочатку вирішили використовувати Пікассо. Тепер ми можемо написати реалізацію, для ImageService
якої використовується Picasso внутрішньо:
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();
}
}
Досить прямо, якщо ви запитаєте мене. Обертання навколо бібліотек не повинно бути складним, щоб бути корисним. Інтерфейс та реалізація мають менше 25 комбінованих рядків коду, тому створити це ледь не було, але ми вже щось отримуємо, роблячи це. Бачите Context
поле в реалізації? Ваша обрана система введення залежностей вже піклується про введення цієї залежності до того, як ми коли-небудь використовуватимемо нашу ImageService
, ваш додаток тепер не повинен дбати про те, як завантажуються зображення та які б залежності не мали бібліотеки. Все, що бачить ваша програма, - це ImageService
і коли йому потрібне зображення, з яким він дзвонить load()
за допомогою URL-адреси - простий та простий.
Однак реальна користь приходить, коли ми починаємо щось змінювати. Уявіть, нам зараз потрібно замінити Picasso на Universal Image Loader, оскільки Picasso не підтримує якусь функцію, яка нам зараз потрібна. Чи нам тепер доводиться прочісувати нашу кодову базу і нудно замінювати всі дзвінки до Picasso, а потім вирішувати десятки помилок компіляції, оскільки ми забули кілька викликів Picasso? Ні. Все, що нам потрібно зробити, це створити нову реалізацію ImageService
та сказати нашій системі введення залежності, щоб використовувати цю реалізацію відтепер:
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);
}
}
Як бачите, реалізація може бути дуже різною, але це не має значення. Нам не довелося змінювати жодного рядка коду ніде в нашому додатку. Ми використовуємо зовсім іншу бібліотеку, яка може мати зовсім інші функції або може використовуватися зовсім по-іншому, але наша програма просто не хвилює. Так само, як і раніше, наша інша програма просто бачить ImageService
інтерфейс зі своїм load()
методом, проте цей спосіб реалізований вже не має значення.
Принаймні для мене це все вже звучить досить приємно, але зачекайте! Є ще більше. Уявіть, що ви пишете одиничні тести для класу, над яким ви працюєте, і цей клас використовує ImageService
. Звичайно, ви не можете дозволити вашим тестовим підключенням здійснювати мережеві дзвінки на якийсь ресурс, розташований на якомусь іншому сервері, але оскільки ви зараз використовуєте, ImageService
ви можете легко дозволити load()
повернути статику, Bitmap
використану для тестів одиниці, застосувавши насмішку ImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
Підводячи підсумок, обертаючи сторонні бібліотеки, ваша база коду стає більш гнучкою до змін, в цілому простішою, легшою для тестування, і ви зменшуєте зв'язок різних компонентів у вашому програмному забезпеченні - все те, що стає все важливішим, чим довше ви підтримуєте програмне забезпечення.