代码改变世界

通过两种代理方式来隔离第三方库

2017-03-11 11:33  Self_made  阅读(708)  评论(1)    收藏  举报

开发的过程中会不可避免的引用一些第三方库,比如网络请求库、图片加载库等等。就拿图片加载库来说,程序中不会只有一个地方来引用到此库,可能有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类即可,而代理类不需要任何变动便可以在不同动态库之间切换。