深入浅出设计模式——从球赛中悟代理模式

109 阅读5分钟
原文链接: my.oschina.net

一、代理模式概念    

    所谓代理模式,看名字大家就知道是通过代理类来间接的访问目标对象。

    在足球比赛中一个比较形象的反映就是:主教练把队长(或其他队员,这里就指定为队长吧)叫到场边告诉他:叫XXX参与防守或叫XXX压上进攻。这时:队长就是代理类,而XXX就是主教练想要访问的目标对象。下面就基于以上场景来理解代理模式。

二、使用场景

    在一个对象不适合或者不能直接引用另一个对象时,我们可以使用代理模式来创建代理类在客户端和目标对象之间起到中介的作用。

三、结构

    代理模式由三个部分组成:

        1. 接口(Action):代理类与目标对象共同实现同一个接口。

        2. 代理类(Captain):由他来代理向两个目标对象传递指令。

        3. 目标对象(Player):具体执行命令的对象。

四、实现

    开始上干货了。

1. 定义接口

public interface Action {

    void attack();  //进攻方法

    void control(); //控制球权方法

    void defend(); //防守方法

}

    接口中定义了三个球员的行动:进攻,控制和防守。

2. 定义实际访问对象

public class PlayerA implements Action {

    @Override
    public void attack() {
        System.out.println("playerA 开始进攻");
    }

    @Override
    public void control() {
        System.out.println("playerA 控制球权");
    }

    @Override
    public void defend() {
        System.out.println("playerA 参与防守");
    }

}
public class PlayerB implements Action {

    @Override
    public void attack() {
        System.out.println("playerB 开始进攻");
    }

    @Override
    public void control() {
        System.out.println("playerB 控制球权");
    }

    @Override
    public void defend() {
        System.out.println("playerB 参与防守");
    }

}

    这里我们定义了两个访问对象,球员A和球员B,他们都实现了Action接口,并对接口方法做了重写。

3. 定义代理类

public class Captain implements Action {

    private Action action;

    Captain(Action action) {
        this.action = action;
    }


    @Override
    public void attack() {
        System.out.println("教练下命令了,叫你进攻。");
        action.attack();
    }

    @Override
    public void control() {
        System.out.println("教练下命令了,叫你控制球权。");
        action.control();
    }

    @Override
    public void defend() {
        System.out.println("教练下命令了,叫你参与防守。");
        action.defend();
    }
}

    队长是我们的代理人,他也实现了Action接口,并且提供了一个构造方法,传入Action并将其赋值到成员变量。他所要实行的Action方法实际是通过成员变量action来实现的。

4. 外部访问对象

public class Coach {

    public static void main(String args[]) {

        PlayerB playerB = new PlayerB();
        PlayerA playerA = new PlayerA();
        
        //队长,你让B开始进攻
        Captain captain = new Captain(playerB);
        captain.attack();
        
        //队长,你让A参与防守
        captain = new Captain(playerA);
        captain.defend();
    }

}

    好了,现在教练要开始下命令了。先看一下执行结果:

   

    可以看出我们访问的方法都来自captian,但是实际执行这些方法都确实球员A和球员B,catpian很好的扮演了传话筒的角色,并且我们还可以看出caption在原方法的基础上还做了一些添加。

    这就是最简单的静态代理,这时候有人会有疑问了,如果传话的不是队长而是助理教练怎么办?助理教练又不具备Action三个方法,没有实现Action接口的话该怎么做。那么我们就需要要用到动态代理了。

五、动态代理

    动态代理的特点就是代理类不需要实现接口,利用反射来动态的在内存中构建访问对象。JDK中的Proxy为我们提供了生成代理对象的API。球员A,球员B和Action不做任何改变,下面我们直接开始制作新的代理类

public class Assistant<T> {

    //维护目标对象的Class
    private Class<? extends T> tClass;
    public Assistant(Class<? extends T> tClass) {
        this.tClass=tClass;
    }

    //给目标对象生成代理对象
    @SuppressWarnings("unchecked")
    public T getProxyInstance(){
        return  (T)Proxy.newProxyInstance(
                tClass.getClassLoader(),
                tClass.getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("开始传达指令");
                    //执行目标对象方法
                    return method.invoke(tClass.newInstance(), args);
                }
        );
    }
    
}

    现在代理者变成了助理,他没法在场上的行动所有没有实现Action方法,在构造方法中传入一个目标对象的Class,然后通过Proxy的newProxyInstance方法动态代理生成目标对象。

    好了,现在教练要下达指令了。

public class Coach {

    public static void main(String args[]) {
        //助理,准备给A球员下达指令。
        Assistant<Action> actionAssistant = new Assistant<>(PlayerA.class);
        Action action = actionAssistant.getProxyInstance();

        //让A球员进攻
        action.attack();
    }
}

    可以看出,我们给Assistant了一个目标对象的Class然后通过getProxyInstance拿到目标对象实现的接口,通过接口得到想要的操作。我们来看一下结果。

    代理成功,动态代理要注意的一点是,目标对象必须要实现一个接口,否则动态代理无效。

六、优势

    前文我们说到,在一个对象不适合或者不能直接引用另一个对象时我们可以通过代理模式建立一个中介类来将两个对象联系起来,并且对目标对象起到了保护作用。我们还可以通过代理模式对原有方法进行加工具有很好的扩展性。

七、局限

    由于在客户端和真实对象之间增加了代理对象,可能导致访问速度变慢。代理模式实现起来比直接访问目标对象复杂,增加工作量。

    当然,还是那句话:世界上没有十全十美的模式,每个设计模式都有它适用的地方,只要我们的使用方式得当,那么装饰者模式可以帮助我们写出漂亮优雅的代码。

PS:由于gitee插件在idea2018下无法使用,源码链接稍后补上。