代理模式笔记

187 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

定义

为另一个对象提供一个替身或占位符以控制对这个对象的访问。让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。Provide a surrogate or placeholder for another object to control access to it. 也叫做委托模式,是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。 代理在结构上类似装饰者,但是目的不同。装饰者为对象加上行为,而代理则是控制访问。 image.pngimage.png 上图是代理模式的通用类图。

  • Subject 抽象主题角色
    • 抽象主题类可以是抽象类也可以是接口,是一个普通的业务类型定义
  • RealSubject 具体主题角色
    • 也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑 的具体执行者
  • Proxy 代理主题角色
    • 也叫做委托类、代理类。负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作

优点

  • 职责清晰
    • 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事物,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰
  • 高扩展性
    • 具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,代理类完成可以在不做任何修改的情况下继续使用
  • 智能化
    • 动态代理

静态代理

普通代理

客户端只能访问代理角色,而不能访问真实角色。玩游戏时升级太枯燥了,找代理刷到最高级。 image.png

普通代理游戏者
public class GamePlayer implements IGamePlayer {

    private String name = "";

    public GamePlayer(String name) {
        this.name =  name;
    }

    @Override
    public void login(String user, String password) {
        System.out.println("登录名为 " + user + " 的用户" + this.name + "登录成功!");
    }

    @Override
    public void killBoss() {
        System.out.println(this.name + "在打怪!");
    }

    @Override
    public void upgrade() {
        System.out.println(this.name + " 又升了一级!");
    }
}
普通代理代理者
public class GamePlayerProxy implements IGamePlayer {

    private IGamePlayer gamePlayer = null;

    public GamePlayerProxy(IGamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }

    /**
     * 代理登录
     * @param user
     * @param password
     */
    @Override
    public void login(String user, String password) {
        this.gamePlayer.login(user, password);
    }

    /**
     * 代理打怪
     */
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    /**
     * 代理升级
     */
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}
使用场景类
public class Client {

    public static void main(String[] args) {
        // 定义一个痴迷的玩家
        IGamePlayer gamePlayer = new GamePlayer("张三");
        // 开始打游戏,记下时间戳
        System.out.println("开始时间:" + getTime());
        // 登录
        gamePlayer.login("zhangsan", "password");
        // 打怪
        gamePlayer.killBoss();
        // 升级
        gamePlayer.upgrade();

        System.out.println("结束时间:" + getTime());
    }

    public static String getTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
    }

}

使用了代理和直接使用真实角色的游戏结果完全一致。在该模式下,调用者只知道代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响 ,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。当然,在实际的项目中,一般都是通过约定来禁止 new 一个真实的角色,这也是一个非常好的方案。

动态代理

动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有 proxy 类,它是根据需要从你传入的接口集创建的。 image.png

动态代理 handler
public class GamePlayInvocationHandler implements InvocationHandler {

    /**
     * 被代理者
     */
    Class cls = null;

    /**
     * 被代理的实例
     */
    Object obj = null;

    /**
     * 代理谁
     * @param obj
     */
    public GamePlayInvocationHandler(Object obj) {
        this.obj = obj;
    }

    /**
     * 调用被代理的方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object result = method.invoke(this.obj, args);
        // 如果是登录方法,则发送信息
        if ("login".equalsIgnoreCase(method.getName())) {
            System.out.println("有人登录账号");
        }
        return  result;
    }
}
使用场景
public class Client {

    public static void main(String[] args) {
//        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");     jdk 8
        // jdk 8 之后
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        // 定义一个痴迷的玩家
        IGamePlayer player = new GamePlayer("张三");
        // 定义一个 handler
        InvocationHandler handler = new GamePlayInvocationHandler(player);
        // 开始打游戏,记下时间戳
        System.out.println("开始时间:" + getTime());
        // 获得类的 class loader
        ClassLoader classLoader = player.getClass().getClassLoader();
        // 动态产生一个代理者
        IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(classLoader, new Class[]{IGamePlayer.class}, handler);
        // 登录
        proxy.login("zhouliu", "password");
        // 打怪
        proxy.killBoss();
        // 升级
        proxy.upgrade();
        // 结束
        System.out.println("结束时间:" + getTime());
    }

    public static String getTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
    }
}

Proxy.newProxyInstance() 新生成了一个类。在代码中加上 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")(jdk8)或者 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true")(jdk8后的版本)。会保存临时生成的 class 文件。 Proxy class 文件

public final class $Proxy0 extends Proxy implements IGamePlayer {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m5;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void upgrade() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void killBoss() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void login(String var1, String var2) throws  {
        try {
            super.h.invoke(this, m5, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("cn.bugstack.design.first.IGamePlayer").getMethod("upgrade");
            m3 = Class.forName("cn.bugstack.design.first.IGamePlayer").getMethod("killBoss");
            m5 = Class.forName("cn.bugstack.design.first.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到新生成的类继承了 Proxy,实现了 IGamePlayer 接口,从而可以完成对游戏的代理!!!

  • super.h.invoke(this, m4, (Object[])null);
    • h 就是我们传入的 GamePlayIHandler