Оборачивая стороннюю библиотеку, вы добавляете дополнительный слой абстракции поверх нее. Это имеет несколько преимуществ:
Ваша кодовая база становится более гибкой к изменениям
Если вам когда-нибудь понадобится заменить библиотеку другой, вам нужно всего лишь изменить реализацию в вашей обертке - в одном месте . Вы можете изменить реализацию оболочки и не нужно ничего менять ни о чем другом, другими словами, у вас есть слабо связанная система. В противном случае вам придется пройти через всю кодовую базу и везде вносить изменения - что явно не то, что вам нужно.
Вы можете определить API оболочки независимо от API библиотеки
Разные библиотеки могут иметь совершенно разные API, и в то же время ни одна из них не может быть именно тем, что вам нужно. Что если какой-то библиотеке нужен токен, который будет передаваться при каждом вызове? Вы можете передавать токен в своем приложении везде, где вам нужно использовать библиотеку, или вы можете хранить его где-то более централизованно, но в любом случае вам нужен токен. Ваш класс-оболочка снова делает все это простым - потому что вы можете просто хранить токен внутри своего класса-оболочки, никогда не подвергая его воздействию какого-либо компонента в вашем приложении, и полностью абстрагировать его от необходимости. Огромное преимущество, если вы когда-либо использовали библиотеку, которая не подчеркивает хороший дизайн API.
Модульное тестирование намного проще
Модульные тесты должны проверять только одну вещь. Если вы хотите выполнить модульное тестирование класса, вы должны смоделировать его зависимости. Это становится еще более важным, если этот класс выполняет сетевые вызовы или получает доступ к другому ресурсу вне вашего программного обеспечения. Оборачивая стороннюю библиотеку, легко смоделировать эти вызовы и вернуть тестовые данные или все, что требуется для этого модульного теста. Если у вас нет такого уровня абстракции, это становится гораздо труднее сделать - и в большинстве случаев это приводит к большому количеству уродливого кода.
Вы создаете слабосвязанную систему
Изменения в вашей оболочке не влияют на другие части вашего программного обеспечения - по крайней мере, до тех пор, пока вы не измените поведение своей оболочки. Вводя слой абстракции, такой как эта обертка, вы можете упростить вызовы в библиотеку и почти полностью удалить зависимость вашего приложения от этой библиотеки. Ваше программное обеспечение будет просто использовать оболочку, и не будет иметь значения, как реализована оболочка или как она делает то, что делает.
Практический пример
Будем честны. Люди могут спорить о преимуществах и недостатках чего-то подобного часами - вот почему я просто хочу показать вам пример.
Допустим, у вас есть какое-то приложение для Android, и вам нужно загрузить изображения. Существует множество библиотек, которые делают загрузку и кэширование изображений быстрым, например, Picasso или Universal Image Loader .
Теперь мы можем определить интерфейс, который мы собираемся использовать, чтобы обернуть любую используемую нами библиотеку:
public interface ImageService {
Bitmap load(String url);
}
Это интерфейс, который мы теперь можем использовать во всем приложении, когда нам нужно загрузить изображение. Мы можем создать реализацию этого интерфейса и использовать внедрение зависимостей, чтобы внедрить экземпляр этой реализации везде, где мы используем ImageService
.
Допустим, мы изначально решили использовать Пикассо. Теперь мы можем написать реализацию, для ImageService
которой внутри используется Пикассо:
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
используемое для модульных тестов, реализовав mocked ImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
Подводя итог, оборачивая сторонние библиотеки, ваша кодовая база становится более гибкой к изменениям, в целом проще, легче тестируется, и вы уменьшаете связывание различных компонентов в вашем программном обеспечении - все вещи, которые становятся все более и более важными, чем дольше вы поддерживаете программное обеспечение.