代码改变世界

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

2017-03-11 11:33  Self_made  阅读(672)  评论(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类即可,而代理类不需要任何变动便可以在不同动态库之间切换。