开发的过程中会不可避免的引用一些第三方库,比如网络请求库、图片加载库等等。就拿图片加载库来说,程序中不会只有一个地方来引用到此库,可能有N个类会用到此库来显示图片。当我们后期需要更换其它第三方库时问题就来了,我们得改多少代码,程序中到底有多少地方引用了这个第三方库,万一改代码的时候引发了其他的bug怎么办。
问题来了我们应该如何避免这种“牵一发而动全身”的囧况。
图1,普通引用第三方库的做法
图2,重新设计之后的引用流程
从上图我们能看到我们通过一个中间层来引用“第三方图片加载库”。这样做的好处是不管第三方图片加载库你换成Picasso还是Glide我们改变的只是这个中间层,其他的我们一行代码都不需要改动。
1、用静态代理来实现。
首先抽象一个ImageLoader接口
public interface ImageLoader() {
void displayImage(String url, ImageView imageView, int defaultImage);
}
比如当前是使用第一种第三方库First来展示项目中的图片,就建一个ImageLoaderSubject类来实现上面的接口
public class ImageLoaderSubject implements ImageLoader{
public ImageLoaderSubject() {
}
@Override
public void displayImage(String url, ImageView imageView, int defaultImage) {
First.loadImage(url, imageView, defaultImage);
}
}
接下来我们写一个代理类来帮我们实现图片加载的任务
public class ImageLoaderProxy implements ImageLoader {
private ImageLoader imageLoader;//作为代理类的一个属性
private ImageLoaderProxy imageLoaderProxy;
public static ImageLoaderProxy newInstance() {
if(imageLoaderProxy == null) {
imageLoaderProxy = new ImageLoaderProxy();
}
return imageLoaderProxy;
}
public ImageLoaderProxy() {
this.imageLoader = new ImageLoaderSubject();
}
@Override
public void displayImage(String url, ImageView imageView, int defaultImage) {
System.out.println("do some thing");
imageLoader.displayImage(url, imageView, defaultImage);
System.out.println("do other thing");
}
}
这样我们在程序中再次引用第三方库时可以直接使用下面的方法
ImageLoaderProxy.newInstance().displayImage(url, imageView, defaultImage);
当我们换成第二种第三方库来完成图片加载时 ,我们只需为该第三方库新建一个调用的类ImageLoaderSubject2 实现,并将代理类中的这行代码 imageLoaderProxy = new UniversalImageLoader();换成imageLoaderProxy = new ImageLoaderSubject2() 即可。
2、用动态代理来实现。
还用一种动态代理的方法,可以不必为特定的接口操作特定代理对象。可使用一个处理者代理多个接口的操作对象。
处理者必须操作java.lang.reflect.InvocationHandler接口
设计一个ImagerLoaderHandler类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ImageLoaderHandler implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
result = method.invoke(target, args);
} catch (IllegalAccessException | IllegalArgumentException e) {
System.out.println(e.toString());
}
return result;
}
}
在需要调用图片加载的地方使用ImageLoaderHandler的bind()方法来绑定被代理的对象。
ImageLoaderHandler imageLoaderHandler = new ImageLoaderHandler();
ImageLoader imageLoaderProxy = (ImageLoader) imageLoaderHandler.bind(new ImageLoaderSubject());
imageLoaderProxy.displayImage(url, imageView, defaultImage);
主要是使用Proxy.newProxyInstance()方法建立代理对象,调用时必须指定类加载器,告知要代理的借口,以及接口上定义的方法被调用时的处理者(InvocationHandler实例),Proxy.newProxyInstance()方法底层会使用原生方式生成代理对象的class实例,并利用它来生成代理对象,代理对象会指定要代理的接口。
这样使代理类更具有通用型,每次只需要改动ImageLoaderSubject类即可,而代理类不需要任何变动便可以在不同动态库之间切换。