Android设计模式—代理模式

1.代理模式
代理模式也叫委托模式,是一种结构型设计模式。它为其他对象提供一种代理以控制对这个对象的访问。
代理模式其实就是提供一个新对象,实现对真实对象的操作或成为真实对象的替身。就是在访问和被访问对象之间加上一个中间层,以隔离访问者和被访问者的实现细节。

代理模式应用场景:
①当一个对象不能或不想直接访问另一个对象时,可以通过一个代理对象来间接访问。
②被访问的对象不想暴露全部内容时,可以通过代理去掉不想被访问的内容。

代理模式能实现控制访问某个对象的能力,在某些情况下,一个对象的某些方法想要进行屏蔽或者某种逻辑的控制,就可以通过代理的方式进行。在此能力上引申出来的作用,也是目前在开发中经常使用的一个作用:在不修改原对象代码的基础上,对原对象的功能进行修改或增强。

代理模式UML类图:
在这里插入图片描述
Subject抽象主题类:该类的主要职责是声明目标对象与代理对象的共同接口方法,该类既可以是一个抽象类,也可以是一个接口。
RealSubject真实主题类:即目标类或被委托类、被代理类,它是代理所表示的真实对象,执行具体的业务逻辑方法,而客户端则通过代理类间接地调用真实主题类中定义的方法。
Proxy委托类或代理类:该类持有一个对真实主题类的引用,在其所实现的接口方法中调用真实主题类的接口方法执行,以此起到代理的作用。

代理模式符合java开发的开闭原则,避免大幅度修改原先的代码,只是在原有的基础上进行扩展。代理模式的优势概括起来有两点:
①解耦
通过引入代理对象来间接访问目标对象,当需要给某个对象添加一些额外的逻辑时,使用代理模式可以不修改原代码,只针对这些额外的功能进行编码。在整个过程中,原对象的逻辑和额外增加逻辑完全解耦,互不干扰。
②高扩展性
通过代理对象可以对原有的业务进行扩展。由于模块间都是解耦的,所以可以随时添加任意的功能或修改原来的功能而不会影响到原模块的正常执行。

代理模式分为静态代理和动态代理。静态代理应用的不多,因为违反了开闭原则,动态代理才是主流。

2.静态代理
代理类在编译阶段生成,在程序运行之前就已经存在的代理模式。
代理对象和被代理对象在代理之前都是确定的,它们都实现相同的接口或继承相同的抽象类。
静态代理需要创建一个和真实对象相同的类,将真实对象当作参数传入到代理中,当外界通过代理进行操作的时候,代理中的真实对象就会进行相应的操作。

①使用继承的方式
目标类Customer.java:
public class Customer {
public String order(String foodName) {
return “已经下单了”+foodName;
}
}
代理类DeliveryClerk.java:
public class DeliveryClerk extends Customer{
@override
public String order(String foodName) {
String result = super.order(foodName);
Log.i(TAG,“已经接收订单,正前往取餐途中”);
Log.i(TAG,“已经取餐,正在派送”);
return result+“已经搅拌均匀”;
}
}
客户端:
//创建一个目标类
Customer customer = new DeliveryClerk(); //给顾客找了一个替身,即DeliveryClerk
String result = customer.order(“麻婆豆腐”);
Log.i(TAG,result);

打印结果为:
已经接收订单,正前往取餐途中
已经取餐,正在派送
已经下单了麻婆豆腐已经搅拌均匀

从打印结果可以看到,调用order方法后实际执行的是代理类的方法,对目标类进行了增强。

②使用接口的方式:
1)创建接口,定义共同的方法;
2)创建目标类,实现接口;
3)创建代理类,实现接口,但是增加一步,在构造方法中传入目标对象的引用,拿到目标对象。在目标对象的方法调用之前和之后可以进行扩展;
4)得到代理对象,通过对代理对象操作,实现对目标对象的相应操作。

抽象主题类:定义接口
public interface OrderInterface {
public String order(String foodName);
public String test();
}
真实主题类:实现OrderInterface接口
public class Customer implements OrderInterface{
@Override
public String order(String foodName) {
return “已经下单了”+foodName;
}
@Override
public void test() {
}
}
代理类:同样实现OrderInterface接口,并持有目标类的引用
public class DeliveryClerk implements OrderInterface{
//把目标对象传入,并存到成员变量
private OrderInterface customer;
public DeliveryClerk(OrderInterface customer){
this.customer = customer;
}
@override
public String order() {
String result = customer.order(“麻婆豆腐”);
Log.i(TAG,“已经接收订单,正前往取餐途中”);
Log.i(TAG,“已经取餐,正在派送”);
return result+“已经搅拌均匀”;
}
@override
public void test() {
}
}
在代理类的构造方法里创建了一个Customer目标对象,在调用代理类的order()方法时,实际是调用的目标对象Customer的order()方法,但是通常会附加一些逻辑,比如打印一些log,直接在代理类的方法里添加就可以。这样做的好处就是目标实现类的逻辑不用做任何改变。
这样对于客户来说,完全不用跟OrderInterface进行直接交互。
客户端:
Customer customer = new Customer();//创建目标类对象
OrderInterface deliveryClerk = new DeliveryClerk( customer); //创建代理类对象
String result = deliveryClerk.order(“麻婆豆腐”); //调用代理对象的方法
Log.i(TAG, result);

打印结果为:
已经接收订单,正前往取餐途中
已经取餐,正在派送
已经下单了麻婆豆腐已经搅拌均匀

调用order方法后,实际执行的是代理类的方法,对目标类进行了增强。

静态代理的优点:
①对客户端隐藏了被代理类接口的具体实现类,在一定程度上实现了解耦,同时提高了安全性。

静态代理的缺点:
①静态代理需要实现被代理类的接口,并实现其方法,造成了代码的大量冗余;
②静态代理只能对某个固定接口的实现类进行代理,灵活性不强。一旦接口或父类发生了变动,代理类的代码就得随之改变。
静态代理都是自己先写好的代理类,这样的代理关系都是固定的,当代理多个真实对象时就要写多个代理类,并且会产生冗余的代码,扩展性和可维护性都不高,也就是静态代理违反了设计模式的开闭原则。

3.动态代理
在程序运行期创建目标对象的代理对象,对目标对象中的方法进行功能性增强。也就是说,代理类是在程序运行时才产生的。
动态代理不需要手写代理类的代码,也不会存在代理类编译的过程,而是直接在运行期,在虚拟机内“凭空”造出一个代理类对象。

①为什么类可以动态生成?
Java虚拟机的类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成3件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流(class字节码);
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区中这个类的各种数据访问入口;
对于第1点获取类的二进制字节流有很多途径,比如:
1)从ZIP包获取,这是JAR、EAR、WAR等格式的基础;
2)从网络中获取,典型的应用是 Applet;
3)运行时计算生成,这种场景使用最多的是动态代理技术,在java.lang.reflect.Proxy类中,就是使用ProxyGenerator.generateProxyClass为特定接口生成形式为*$Proxy的代理类的二进制字节流;
4)由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类从数据库中获取等;
所以,动态代理就是想办法根据接口或目标对象计算出代理类的字节码,然后再加载到JVM中使用。

②明白了类的动态生成,然后看一下动态代理的原理
动态代理是基于反射实现了在程序运行过程中才决定代理什么对象。

动态代理有两种实现方式:基于JDK(接口)的动态代理和基于CGLIB(父类)的动态代理。

(1)基于JDK(接口)的动态代理
这是JDK自带的动态代理技术,这种实现方式有一个缺点,就是它要求被代理对象,也就是目标类,必须实现接口。生成的代理对象和原对象都实现相同的接口,它们是兄弟关系。

java提供了动态代理接口InvocationHandler和动态代理类Proxy供开发者使用,它们都在java.lang.reflect包中,可见动态代理和反射有密不可分的关系。

Proxy类定义如下:
public class Proxy implements Serializable {
protected InvocationHandler h;//持有一个InvocationHandler类型的引用
protected Proxy(InvocationHandler h) {
this.h = h;
}
//根据指定的类加载器和接口来获取代理对象的Class对象
public static Class<?> getProxyClass( ClassLoader loader, Class… interfaces) throws IllegalArgumentException {
//…
}
//根据指定的类加载器和接口生成代理对象
public static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
//…
}
//…
}
Proxy类的作用是用来动态的创建一个代理对象,它内部会持有一个InvocationHandler引用(在构造方法中传入),它提供了许多的方法,但是常用的是getProxyClass方法和newProxyInstance方法:
getProxyClass:作用是在运行时根据.class结构生成一个代理Class二进制流,并通过传入的ClassLoader把代理Class二进制流加载成一个代理Class对象,该代理Class对象继承Proxy并实现了传入的第二个参数对应的Interface列表。
newProxyInstance: 作用是在运行时根据代理Class对象生成代理对象实例,这个方法中会先调用getProxyClass方法生成代理Class对象,在获取到代理Class对象后,根据第三个参数InvocationHandler通过反射创建代理对象实例,所以newProxyInstance最终的结果是生成一个代理对象实例,该代理对象会继承Proxy类并实现给定的接口列表,同时内部持有一个InvocationHandler引用。
可见,动态代理通过JDK的Proxy对象和反射机制支撑起了动态代理的核心功能。

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数的意义:
ClassLoader:固定写法,指定目标类对象的类加载器,用于加载目标类及其接口的字节码文件。使用目标类的字节码对象调用getClassLoader()方法即可得到。
Class<?>[] interfaces:固定写法,指定目标类实现的所有接口的字节码对象的数组。使用目标类的字节码对象调用getInterfaces()方法即可得到。
InvocationHandler:是一个接口,表示当动态代理对象在调用方法的时候,会关联到哪个InvocationHandler对象上。

public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable ;
}
InvocationHandler是一个接口,只有一个invoke抽象方法,它会在代理类对象调用方法时执行。也就是说在代理类对象中调用任何接口中的方法时都会执行到invoke方法中,所以可以在该方法中完成对增强或者扩展代码逻辑的编写。
InvocationHandler接口的作用是在invoke方法中执行目标对象的方法。需要为目标对象定义一个实现了该接口中的invoke方法的动态代理类,同时在创建这个动态代理类的实例时要在方法或构造中传入真实对象的引用,即InvocationHandler的实现类需要持有真实对象的引用,这样才能执行真实对象的方法
四个参数的意义:
proxy:生成的动态代理类,也就是Proxy.newProxyInstance方法的返回值。
method:被代理的方法,对应的是触发invoke时执行的方法的Method对象。比如调用xxx方法,该方法触发invoke方法的执行,那么method就是xxx方法对应的反射对象(Method对象)。
args:代理对象调用方法时,传递的实际参数。

现在ClassLoader、方法名和方法参数都有了,就可以使用反射调用了。

使用动态代理的基本步骤如下:
1、定义代理对象和真实对象的公共接口(与静态代理步骤相同);
2、真实对象实现公共接口中的方法(与静态代理步骤相同);
3、定义一个实现了InvocationHandler接口的动态代理类;
4、通过Proxy类的newProxyInstance方法创建代理对象,调用代理对象的方法。

①抽象主题类
public interface OrderInterface {
public String order(String foodName);
public String test();
}
②真实主题类
public class Customer implements OrderInterface {
@Override
public String order(String foodName) {
return “已经下单了”+foodName;
}
@Override
public void test() {
}
}
③代理类
使用JDK的API动态生成一个代理对象。首先定义一个InvocationHandler类。
public class ProxyHandler implements InvocationHandler {
private Object targetObj; //被代理类
//生成代理类的方法,返回值即为代理类
public Object newProxyInstance(Object object) {
this.targetObj = object;
return Proxy.newProxyInstance( targetObj.getClass().getClassLoader(), targetObj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(“order”.equals(method.getName())) { // 需要在order方法里增加扩展逻辑
//调用原来的method方法逻辑
Object result = method.invoke(customer, args);
//添加扩展逻辑
System.out.println(“我是扩展逻辑”);
return result;
} else { // 不需要增加扩展逻辑的方法
return method.invoke(targetObj, args); //使用method反射调用,在原对象(目标对象)中执行该方法,不修改其逻辑,也就是说原封不动的调用原来的逻辑
}
}
}
注意:Proxy.newProxyInstance方法的返回值是代理类,代理类和目标类是兄弟关系。
④客户端:调用代理类对象,执行对应方法
Customer customer = new Customer(); //被代理类
ProxyHandler proxyHandler = new ProxyHandler();
OrderInterface deliveryClerk = proxyHandler.newProxyInstance(customer); //代理类
String result = deliveryClerk.order(“麻婆豆腐”);
System.out.println(result);

动态代理在内存中生成代理类,其原理就类似在内存中新建了一个DeliveryClerk类,而Proxy.newProxyInstance方法中invoke做的事情就是DeliveryClerk类里每个方法做的事情。

(2)基于CGLIB(父类)的动态代理

动态代理类的优点:
①在不改变方法源码的情况下,实现对方法功能的增强,提高代码复用性;
②非常灵活,支持任意接口类型的实现类对象做代理。

4.静态代理和动态代理比较
静态代理缺点:
①扩展性差,如果接口新增一个方法,除了所有实现类(真实主题类)需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
②代理对象只服务于一种类型的对象,如果要服务多类型的对象,必须要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

动态代理的优点:
①可以通过一个代理类完成全部的代理功能。接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。当接口方法数量较多时,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
②动态代理的应用使类的职责更加单一,复用性更强。
动态代理的缺点:
①JDK方式不能对类进行代理,只能对接口进行代理,如果类没有实现任何接口,那么就不能使用这种方式进行动态代理。

5.动态代理应用
Retrofit中使用了动态代理。使用Retrofit需要先定义一个接口:
public interface MyService {
@GET(“users/{user}/list”)
Call getMyList(@Path(“user”) String user);
}
然后新建Retrofit对象,使用Retrofit对象产生一个接口对象,就调用具体方法去完成请求。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(“http://xxx.com”)
.build();
MyService myService = retrofit.create( MyService.class);
Call myList = myService.getMyList( “my”);
retrofit.create方法就是通过动态代理的方式传入一个接口,返回了一个对象。

具体看Retrofit的create方法,就是在这里使用了动态代理:
public T create(final Class service) {
validateServiceInterface(service);//判断是否为接口接口
//动态代理的方法,返回定义的service的实例
return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] {service}, new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//代理类的生成默认会继承Object,调用代理类的方法
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
return loadServiceMethod( method).invoke(args);
}
});
}
通过Proxy.newProxyInstance方法,该动态代理对象可以拿到请求接口实例上所有注解,然后通过代理对象进行网络请求。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值