设计模式之代理模式-老宋丶宝强篇

一 . 简介

代理模式是Android 开发中常用的一种设计模式,能为其他对象提供一种代理以控制对这个对象的访问,能在客户端和目标对象之间起到中介的作用。

对于一些初学者来说,上述定义可能很难理解,或者理解了定义却不知道怎么运用,试用哪些场景.

本文从基础定义介绍代理模式,同时从常用的框架中举例,介绍代理模式的使用.
希望能帮助还没有理解的猿(媛)们理解.欢迎大家指正.


二. 详解

分类: 代理模式分静态代理和动态代理

先举个 宝强与老宋 假栗子:

对象宝强是明星,而各位大佬很有钱,想找宝强拍戏,但是你无法直接找到他,所以就需要经纪人老宋帮忙
对象老宋就负责了宝强的工作安排和各种事务处理工作,同时他还可以具有特殊的行为,比如对马小姐的访问.
这个老宋就相当于代理模式中的代理,你可以通过对象老宋从而调用对象宝强.

如图:

这里写图片描述

  • 真实对象(宝强)RealSubject代理(老宋)Proxy需要实现同一个接口
  • (你)Client访问(宝强)RealSubject时,可以通过访问(老宋)Proxy进行对(宝强)RealSubject的控制
  • (老宋)Proxy可以作为一个轻量级的对象代理(宝强)RealSubject的一部分,也可以修改原来属性方法,比如添加CallMaXiaoJie()
  • (老宋)Proxy作为(你)ClientRealSubject的中间层,可以降低模块间和系统的耦合性

下面介绍具体的代码实现和用法分析

1.静态代理

接口Subject,包含宝强演戏和聚会等抽象行为

public interface Subject {
    void act();
    void party();
}

Baoqiang:代表宝强 ,实现了Subject接口,具有演戏和聚会的真正行为.

public class Baoqiang implements Subject {
    private static final String TAG = "Baoqiang:";
    @Override
    public void act() {
        //宝强片场演戏
        System.out.println(TAG+"去片场演戏");
    }

    @Override
    public void party() {
        //宝强参加聚会
        System.out.println(TAG+"去参加聚会");
    }
}

LaoSong:代表老宋,同样实现了Subject的接口,拥有了Baoqiang 相同的行为方法,控制ClientBaoqiang的访问.

public class LaoSong implements Subject {
    private static final String TAG = "LaoSong:";
    private Baoqiang mBaoqiang;

    public LaoSong(Baoqiang baoqiang) {
        mBaoqiang = baoqiang;
    }

    /**
     * 安排宝强演戏
     */
    @Override
    public void act() {
        callMaXiaoJie(TAG);
        mBaoqiang.act();
        callMaXiaoJie(TAG);
    }

    /**
     * 安排宝强聚会
     */
    @Override
    public void party() {
        callMaXiaoJie(TAG);
        mBaoqiang.party();
        callMaXiaoJie(TAG);
    }

    /**
     * 约马小姐
     */
    private void callMaXiaoJie(String string) {
        System.out.println(string+"约马小姐");
    }
}

Client(你)通过访问代理LaoSong从而通知Baoqiang去act(),party();

public class Client {
    public static void main(String[] args) {
        LaoSong proxy = new LaoSong(new Baoqiang());
        proxy.act();
        System.out.println("");
        proxy.party();
    }
}

打印结果:
这里写图片描述

结果表明: .Client 你“通过访问代理LaoSong 完成了对真实对象Baoqiang的访问,安排了Baoqiang去act()和party(),同时 代理LaoSong 也可以拥有自己的行为,比如: 约马小姐


:上面就是静态代理的简单使用,可能大佬会有如下问题?

  • 问: 既然已经有真实对象,为何不直接修改方法捏,为何还要传入到代理中去使用捏.不是反而变慢了么?
  • 答: 修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则.不利于维护和扩展(万能答案)

  • 问:为何要实现同一接口,随便写一个类依赖真实对象不是也可以么.

  • 答: 简单而且具有规范性,易于维护和扩展(万能答案),当然也有坏处,如果接口方法很多,代理类就需要实现很多不必要实现方法。增加了代码维护的复杂度。

  • 问: 如果真实对象不能直接拿到,而且也无法实现共同的接口,怎么办?

  • 答: 一个对象无法被直接引用,也无法实现共同的接口的时候,我们可以使用JAVA动态代理,同时也不用像静态代理类一样,实现很多不必要的方法.

2.动态代理

当对象无法被直接引用,也没有共同接口的时候,我们可以使用JAVA 动态代理 控制对原有对象的访问
JAVA 动态代理 在Android使用比较常见
比如:Retrofit框架
还有鸿洋大佬的一篇 [教你打造Android的IOC框架],里面就用到了动态代理
: 后面会用这两篇示例讲解动态代理

1.基础介绍

假设:现在我们无法拿到真实对象的引用(即无法直接联系宝强),真实对象也没有实现对外的共同接口,按照静态代理我们就无法生成一个代理类老宋.所以我们也无法通过老宋取得”宝强的联系,所以大佬们就要通过实现动态代理实现和宝强的联系

在Java动态代理机制中,有一个重要的 Proxy类和InvocationHandler接口,这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看这两个类进行的描述:

java.lang.reflect.Proxy

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy 提供 一组静态方法 为 一组接口 动态的生成对象和代理类,它也是所有生成的代理类的父类

这里也就关注Proxy的newProxyInstance这个静态方法

 /**
  * @param loader 指定类加载器 
  * @param interfaces  指定类的实现接口
  * @param invocationHandler 动态代理类的处理器
  * @return 代理类Proxy
  */
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler);

方法需要三个参数:
参数 ClassLoader loader 和 参数 Class<?>[] interfaces 用来生成动态代理proxy.
参数invocationHandler 用来执行的代理类的方法逻辑.                                     

java.lang.reflect.InvocationHandler

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

调用处理器接口,自定义invoke方法,用于实现对于真正委托类的代理访问,当动态代理类方法执行时,就会执行InvocationHandler的invoke方法.

 /**
  * @param proxy:指代理类实例
  * @param method:指代的是我们所要调用真实对象的某个方法的Method对象
  * @param args:指代的是调用真实对象某个方法时接受的参数
 */
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

好了,基本介绍完毕
没有懂得大兄弟不用担心,
下面从常用的框架源码中分析,动态代理proxy具体使用,一起来感受感受它的强大.


示例 一

2014年9月22日,hyman在他的CSDN里发表了文章 教你打造Android的IOC框架[ViewInject](传送门)
这是关于对Android控件的依赖注入的文章,里面就用到了动态代理,以及包含动态代理的介绍,大兄弟们可以直接点进去阅读.
我这里根据自己的理解也介绍一下:
首先看代码:

@ContentView(value = R.layout.activity_main)  
public class MainActivity extends BaseActivity  
{  
    @ViewInject(R.id.id_btn)  
    private Button mBtn1;  
    @ViewInject(R.id.id_btn02)  
    private Button mBtn2;  

    @OnClick({ R.id.id_btn, R.id.id_btn02 })  
    public void clickBtnInvoked(View view)  
    {  
        switch (view.getId())  
        {  
        case R.id.id_btn:  
            Toast.makeText(this, "Inject Btn01 !", Toast.LENGTH_SHORT).show();  
            break;  
        case R.id.id_btn02:  
            Toast.makeText(this, "Inject Btn02 !", Toast.LENGTH_SHORT).show();  
            break;  
        }  
    }  

是不是觉得很熟悉,类似一种ButterKnife的简化版,不过原理不一样的,这里我们主要介绍动态代理,我们主要看下面这段;

 @OnClick({ R.id.id_btn, R.id.id_btn02 })  
 public void clickBtnInvoked(View view){}  

这是实现了view的点击事件的处理,可具体是怎么实现的捏,先让我们回顾下平常点击事件;

mBtn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                  Log.e(TAG, "点击了view" );
            }
        });

我们都知道,当我们点击view的时候,事件会由Activity传到我们所对应的view,而onClick是在onTouchEvent内部通过performClick触发的,如下图:
这里写图片描述

所以我们只要li.mOnClickListener不为null,li.mOnClickListener.onclick(view)就会执行,而mOnClickListener就是OnClickListener 的接口,具体如下:

 public OnClickListener mOnClickListener;

 public interface OnClickListener {
          void onClick(View v);
    }

  public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

所以为什么大佬们每次处理点击事件的时候,都会先调用view.setOnClickListener(listener)方法,其实就是为了传递一个OnClickListener对象,然后回调onclick(view)方法,我们就可以在onclick方法中处理自己的逻辑.

其实ViewInject的本质是一样的,只不过通过IOC的思想,使用自定义注解的方式,把view.setOnClickListener(listener)的控制,反转给了其他的类(ViewInjectUtils)处理,下面我们来看具体实现:

 private static void injectEvent(final Activity activity) {
    Class<? extends Activity> clazz = activity.getClass();
        //获取当前Activity所有的方法
    Method[] _methods = clazz.getDeclaredMethods();

    for (final Method _method : _methods) {
            //找到有Onclick注解的方法
    Onclick _onclick =_method.getAnnotation(Onclick.class);

         if (_onclick != null) {
            //获取当前注解所传入的控件Id数组
            int[] viewId = _onclick.value();
             _method.setAccessible(true);

            /获取OnClickListener的代理
     Object listener = Proxy.newProxyInstance( 
         View.OnClickListener.class.getClassLoader(),                          
         new Class[]{View.OnClickListener.class},
         new InvocationHandler() {
             @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return _method.invoke(activity,args);
                 }
                 });

        //遍历Id,转化成view
        for (int id : viewId) {
         View v = activity.findViewById(id);
          try {
       Method setListener= v.getClass().                                   
             getMethod("setOnClickListener", 
                       View.OnClickListener.class);

   //实现View.setOnClickListener(listener)方法;
     setListener.invoke(v, listener);

               } catch (Exception e) {
                 e.printStackTrace();
                }
                }

            }
        }
    }
  • 1-13行:主要通过反射拿到Activity中的方法,遍历找到@Onclick注解,获取到注解中的控件Id
  • 27-28行:根据activity.findviewById(Id)指定对应的view控件
  • 30-36行: 然后反射拿到setOnClickListener方法,通过view. setOnClickListener(listener)设置监听

注: 真实对象listener(宝强)是个系统的OnClickListener 接口,我们在ViewInjectUtil类中无法直接拿到它,所有需要通过java的动态代理生产一个OnClicklistener的代理类.

         /获取OnClickListener的代理
     Object listener = Proxy.newProxyInstance( 
         View.OnClickListener.class.getClassLoader(),                          
         new Class[]{View.OnClickListener.class},
         new InvocationHandler() {
             @Override
            public Object invoke(Object proxy, 
            Method  method, Object[] args) throws Throwable {
            return _method.invoke(activity,args);  }              
          });

先看它前两个参数,由于要代理OnClickListener类,所以这里传入OnClickListener的类加载器和OnClickListener的接口数组,生成OnClickListener的代理类

动态代理类和静态代理类不一样, 它非预存在于任何一个.class 文件中也不需要事先定义好接口,它由 JVM 在运行时动态生成的.

我们再看第三个参数:传入的是个InvocationHandler接口对象,用于委托代理类访问,什么意思捏?稍后解释.
我们先来解决最后一个问题,我们现在已经拿到了OnClickListener类,也设置了view.setOnClickListener(listener)监听,这时候我们点击view的时候,代理类的onclick里方法就会被回调,我们也能在里面处理自己的逻辑,如下:

  @Override
  public void onClick(View v) {
     Log.e(TAG, "点击了view" );
     }

但是问题是我们现在并不需要onClick方法,而是希望能把回调放到下面的clickBtnInvoked(view)方法中执行,该怎么办捏?

 @OnClick({ R.id.id_btn, R.id.id_btn02 })  
 public void clickBtnInvoked(View view){
    switch (view.getId()) {  
        case R.id.id_btn:  
            Toast.makeText(this, "Inject Btn01 !",    
                        Toast.LENGTH_SHORT).show();  
            break;  } 
     }

这个时候,第三个参数InvocationHandler就出场了,InvocationHandler接口能委托代理类访问,当代理类OnClickListener.onClick(view)方法执行的时候,InvocationHandler中的invoke()方法也就被调用,然后我们只要在invoke方法中再调用clickBtnInvoked(view方法,就完成了,如下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   //_method就是clickBtnInvoked()方法
   return _method.invoke(activity,args);
  }

当然,我们还可以在InvocationHandler的invoke的方法中调用其他我们想要执行的逻辑(比如:访问马小姐),只要代理类的任何方法调用的时候,都会被委托到invoke方法中,我们只要做好逻辑判断即可.
好的,示例一的动态代理介绍完毕,下面我们看示例二.


示例 二

示例二是我们熟悉的Retrofit框架,这里先贴几句大家都熟悉的代码:

首先是APIService接口,里面通过注解的方式,用来定义了网络请求的方法

public interface APIService {
    @GET("forecast7d?city=CH250901&key=hepj2khdr187nvlm")
    Call<WeatherBean> getWeath();

}

然后是 Retrofit的基本配置

 retrofit = new Retrofit.Builder().
                baseUrl(HttpConfig.BASE_URL).
                client(new OkHttpClient()).                  addConverterFactory(GsonConverterFactory.create())
                .build();

APIService _apiService = retrofit.create(APIService.class);

我们看最后行代码retrofit.create(APIService.class)传递的是一个接口类,然后却直接返回了一个APIService 对象,这又事怎么实现的捏?我们看源码:

 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

此时看到 Proxy.newProxyInstance()方法是不是特别熟悉,没错,这里用的也是java动态代理机制生成了APIService 的代理类

Call<WeatherBean> weatherBeanCall = _apiService.getWeath();

而当代理类调用APIService 里面的请求时,同时InvocationHandler的invoke()方法也被调用,然后根据不同的情况,retrofit2.0在invoke里面就有了不同的具体实现.

至于具体实现是什么,这里就不做解释,

本文主要讲述的是代理模式,到这里也基本完成了,欢迎你的阅读


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值