通过包装第三方库,您可以在其上面添加一个抽象层。这有一些优点:
您的代码库对更改变得更加灵活
如果您需要用另一个库替换该库,则只需在一个位置更改包装器中的实现。您可以更改包装程序的实现,而不必更改其他任何东西,换句话说,您有一个松散耦合的系统。否则,您将必须遍历整个代码库并在各处进行修改-显然这不是您想要的。
您可以独立于库的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进行调用-简单明了。
但是,当我们开始改变事物时,真正的好处就来了。假设我们现在需要用Universal Image Loader替换Picasso,因为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;
}
}
总而言之,通过包装第三方库,您的代码库可以更灵活地进行更改,更简单,更易于测试,并且可以减少软件中不同组件的耦合-随着软件维护时间的延长,所有事情变得越来越重要。