参考资料:
https://blog.csdn.net/gdutxiaoxu/article/details/81394050
https://blog.csdn.net/lmj623565791/article/details/79278864
https://blog.csdn.net/liujiahan629629/article/details/19428485
http://www.jianshu.com/p/b28fbc388d30

一、两种代理模式

代理模式分为静态代理与动态代理
- 静态代理
在编译期生成代理对象

代码示例:

//Subject类,定义了RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以
  使用Proxy。
public interface Subject {
    public void request();
}

//实体类RealSubject,定义Proxy所代表的真实实体。
public class RealSubject implements Subject {
    public void request() {
        System.out.println("真实的请求");
    }
}

//代理类Proxy,保存一个引用使得代理可以访问实体。
public class Proxy implements Subject {
    RealSubject realSubject;
    public void request() {
        if(realSubject == null){
            realSubject = new RealSubject();
        }
        realSubject.request();
    }
}

客户端调用:
public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
}

  • 动态代理
    在运行时生成代理对象,可以拦截方法,可以中途修改方法的参数,决定方法要不要执行等(return null不执行)。

动态代理技术方案:
1)通过JDK提供的API实现的
2)通过CGLIB库,生成的代理类与被代理类就是父子关系。

代码示例:

final Singer singer = new Singer();

        Humen proxy = (Humen) Proxy.newProxyInstance(singer.getClass().getClassLoader()
                , singer.getClass().getInterfaces(), new InvocationHandler() {

                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        System.out.println(method.getName());
                        System.out.println(Arrays.asList(args));
                        //可能对args做一些手脚 

                        //指定singer为被代理对象
                        return method.invoke(singer, args);
                    }
                });
        proxy.sing(40);
        proxy.dance(30);

二、代理模式应用场景

1)数据库事务。数据库当中的事务,代码头是开启事务,代码后面是回滚和释放释放。可以把业务代码放在被代理类中执行,代码头与代码后放在代理类中执行。
2)统计方法的执行时间。

三、代理模式实战演练

1)在编辑资料的时候,检测数据是否被修改过。

构造一个Map的代理类,对put方法进行代理。

```java
/**
* Created by xx on 2016/11/8 0008.
* func:
* 代理带监听的Map,在退出时,判断是否修改了数据。
*/

public class ProxyObservableMap<K,V> {
private Map<K,V> map;
private boolean changed = false;

<pre><code>//在业务逻辑层调用,如果保存了数据,就消除数据已经改变的标记。
public void clearCache(){
changed = false;
}

public void put(K key,V value){
if(map == null){
map = new HashMap<>();
}
if(map.containsKey(key)){
if(!map.get(key).equals(value)){
changed = true;
}
}
if(changed) return;
map.put(key,value);
}

public boolean isChanged() {
return changed;
}
</code></pre>

}

<pre><code class="line-numbers"> 一般的做法应该是先把原始的数据存入一个集合,在保存的时候再次把数据存入一个集合,然后对比两个集合得到答案。作为优秀的程序员,循环遍历一定是最后的办法。

#### 2)公共参数配置
定义的retrofit网络接口,都是通过map来传递参数的,但是所有的接口都有一个公共的参数。为了解决每次传参的时候,公共参数不重复传入。

代理类:主要是代理了MAP类的put方法(代理不一定要完全代理!!!)
```java
/**
* Created by xx on 2017/12/8.
* MAP代理map,封装共同信息。
*/
public class CZMap {
private Map<String,Object> realMap;

public CZMap() {
realMap = new HashMap<String, Object>();
realMap.put(Constants.SystemConfigKey.TERMINAL_ID, SystemPropertyConfigUtil.getProperty(Constants.SystemConfigKey.TERMINAL_ID));
realMap.put(Constants.SystemConfigKey.MANUFACTURER_ID, SystemPropertyConfigUtil.getProperty(Constants.SystemConfigKey.MANUFACTURER_ID));

if(OperatorManager.getInstance().isHasLogin()){
realMap.put("operatorName", OperatorManager.getInstance().getOperatorInfoBean().getOperatorName());
realMap.put("authToken", SPUtil.get(SPUtilKeys.AUTH_TOKEN,""));
}
}

public void put(String key,Object value){
realMap.put(key,value);
}

public Map getRealMap(){
return realMap;
}

调用代理MAP类:

```java
@Override
public void savePic(String base64, String fileName, String returnId, Callback<SavePicResultBean> callBack) {
CZMap czMap = new CZMap();
czMap.put("base64", base64);
czMap.put("fileName", fileName);
czMap.put("returnId", returnId);
MainNetClient.getInstance().savePic(czMap.getRealMap(), new TimeCountCallBackHandler(callBack));
}

<pre><code class="line-numbers"> #### 3)去重操作
曾经做了1个签到系统,由于之前的bug和服务也未做签到的去重操作,导致解析出来的日期实体集合有重复的元素。我就想要写一个代理类List,保证像Set一样保证无重复的元素。那有人会问:为什么不直接采用HashSet呢?

HashSet保证元素的唯一性,是根据元素的hashCode和equals方法来综合判断的,但是我将存储的这个元素名叫CalendarDay,是一个第3方的jar包中的类,因此无法复写它的这2个方法。当然直接去修改这个类,也违反了设计模式中的“开闭原则”。下面就是我这个代理类逐步设计的思路:

```java
/**
* Created by 陈章 on 2016/11/9 0009.
* func:
* List代理类:可以模仿map去重的功能。
*/

public abstract class ProxyUniqList<T> {
protected List<T> realObjectList;
protected List<Adapter> adapterList;

public ProxyUniqList() {
if(realObjectList == null){
realObjectList = new ArrayList<>();
}
if(adapterList == null){
adapterList = new ArrayList<>();
}
}

public void add(T value){
if(adapterList.size() > 0){
if(adapterList.get(adapterList.size() - 1).equals(getAdapter(value))){
return;
}
}
realObjectList.add(value);
adapterList.add(getAdapter(value));
}

public abstract Adapter<T> getAdapter(T value);

public T get(int position){
return realObjectList.get(position);
}

public void clear(){
realObjectList.clear();
}

public int size(){
return realObjectList.size();
}

//适配器,判断两个元素是否“相等”。
public static abstract class Adapter<T>{
protected T instance;

public Adapter(T instance) {
this.instance = instance;
}

protected abstract long getUniqId();

public T getInstance() {
return instance;
}

public boolean equals(Adapter t){
return this.getUniqId()==t.getUniqId();
}

}
}

可以看到上面这个类,考虑到不光要唯一存储CalendarDay这个类,可能在其它地方或别的项目中还会用到,所以加了泛型。

另外一个很纠结的问题就是ProxyUniqList这个类,它存储的元素T,怎么判别它们是否“相等”。

结合“适配器”设计模式(专门针对已有的无法修改的类),我在ProxyUniqList的内部定义了一个适配器类,通过getUniqId()抽象方法返回一个id判断元素是否唯一,之所以getUniqId()要抽象,因为不同的元素构造uniqid的方式不唯一。

再者可以看到ProxyUniqList方法add,在这个方法中,每存储一个元素,我都会用一层Adapter封装这个元素,并将adapter存入到一个集合中。在add方法最前面,我会判断对应索引的adapter,从而判断2个元素是否相等。但是此时adapter还是抽象的,无法实例化,于是利用“模板方法”设计模式在ProxyUniqList中抽象出一个方法getAdapter(),这样就避免了add方法全部抽象,将抽象局部化到getAdapter方法。

基类写好了,针对CalendarDay这个类的集合的代理类:

public class CalendarDayProxyUniqList extends ProxyUniqList<CalendarDay>{

    @Override
    public Adapter<CalendarDay> getAdapter(CalendarDay value) {
        return new CalendarDayAdapter(value);
    }


    class CalendarDayAdapter extends ProxyUniqList.Adapter<CalendarDay> {
        public CalendarDayAdapter(CalendarDay instance) {
            super(instance);
        }

        @Override
        protected long getUniqId() {
            return instance.getCalendar().getTimeInMillis();
        }
    }
}

这样,将原来代码里的ArrayList替换成CalendarDayProxyUniqList,用法与arraylist用法一样,就实现了类似Set的保证元素唯一的功能。


0 条评论

发表回复

您的电子邮箱地址不会被公开。