代理模式(普通代理到动态代理)

文章目录

      • 代理类示例
      • 代理模式的定义
      • 代理模式的应用
          • 代理模式的优点
          • 代理模式的使用场景
      • 代理模式的扩展
          • 普通代理
          • 强制代理
          • 代理是有个性的
          • 动态代理

代理类示例

  • 以前很喜欢打游戏,和队友们一起打怪升级,那么通过一段简单的代码来表示我们打游戏的过程。
    代理模式(普通代理到动态代理)_第1张图片

  • 代码如下:首先是一个游戏过程的接口类

public interface IGamePlayer {
    // 登录游戏
    void login(String user, String pwd);
    // 杀怪
    void killBoss();
    // 升级
    void upgrade();
}

  • 接着是具体游戏玩家
public class GamePalyer implements IGamePlayer {
    // 游戏账户名
    private String userName;

    public GamePalyer(String userName) {
        this.userName = userName;
    }

    @Override
    public void login(String user, String pwd) {
        System.out.println("登录账号为:" + user + userName + "登录成功");
    }

    @Override
    public void killBoss() {
        System.out.println(this.userName + "打boss");
    }

    @Override
    public void upgrade() {
        System.out.println("升级了。。。");
    }
}
  • 然后我们开始打游戏
public static void main(String[] args) {
        IGamePlayer gamePlayer = new GamePalyer("张三");
        System.out.println("游戏开始时间" + "2010年9月4日23:07:30");
        gamePlayer.login("zhangsan", "password");
        gamePlayer.killBoss();
        gamePlayer.upgrade();
        System.out.println("游戏结束" + "2019年9月4日23:08:10");
    }
  • 输出如下
游戏开始时间2010年9月4日23:07:30
登录账号为:zhangsan张三登录成功
张三打boss
升级了。。。
游戏结束2019年9月4日23:08:10

结果是我们想要的打游戏的过程,不知道你们有没有这种体验,每天不停地打游戏,腰酸背痛腿抽筋,学习成绩也下降了,白天起不来,晚上睡不着的。如何解决呢,于是产生了一种职业:代练。我也找过代练为我升级。接下来我们修改一下类图。
代理模式(普通代理到动态代理)_第2张图片

  • 类图中增加了一个GamePlayerProxy代理类,表示代练者,代练也是需要登录、打怪、升级。所以也是实现了IGamePlayer接口。其代码如下:
// 代理类
public class GamePlayerProxy implements IGamePlayer {
    
    private IGamePlayer gamePlayer;
    
    public GamePlayerProxy(IGamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }
    // 代练登录
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user,pwd);
    }
    // 代练杀怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }
    // 代练升级
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}

  • 场景类修改如下
public static void main(String[] args) {
        IGamePlayer gamePlayer = new GamePalyer("张三");
        IGamePlayer proxy = new GamePlayerProxy(gamePlayer);
        System.out.println("游戏开始时间" + "2010年9月4日23:07:30");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("游戏结束" + "2019年9月4日23:08:10");
}
  • 看起来没有任何改变。但是已经不用自己去杀怪就可以升级了。

代理模式的定义

代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供 一种代理以控制对这个对象的访问。)

代理模式的通用类图如下:
代理模式(普通代理到动态代理)_第3张图片

  • 代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策 略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理 模式可以提供非常好的访问控制。在一些著名开源软件中也经常见到它的身影,Retrofit就采用了代理模式。我们先看一下类图中的三个角色的定义:
  • Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
  • RealSubject具体主题角色:也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
  • Proxy代理主题角色:也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

抽象主题类代码如下:

public interface Subject { 
//定义一个方法 public void request(); 
}

真实主题类

public class RealSubject implements Subject {
 //实现方法
 	 public void request() {
 	 //业务逻辑处理
 	  } 
 }

RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上

public class Proxy implements Subject {
    //要代理哪个实现类 
    private Subject subject = null;

    // 默认被代理者 
    public Proxy() {
        this.subject = new Proxy();
    }

    //通过构造函数传递代理者 
    public Proxy(Object... objects) {
    }

    //实现接口中定义的方法 
    public void request() {
        this.before();
        this.subject.request();
        this.after();
    }

    //预处理 
    private void before() {
        //do something 
    }

    // 善后处理 
    private void after() {
        //do something } 
    }
}

一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题 角色,是由场景类决定的。当然,最简单的情况就是一个主题类和一个代理类,这是最简洁 的代理模式。在通常情况下,一个接口只需要一个代理类就可以了,具体代理哪个实现类由 高层模块来决定,也就是在代理类的构造函数中传递被代理者,例如我们可以在代理类 Proxy中增加如下所示的构造函数。

public Proxy(Subject _subject){
 this.subject = _subject;
}

你要代理谁就产生该代理的实例,然后把被代理者传递进来,该模式在实际的项目应用 中比较广泛。

代理模式的应用

代理模式的优点
  • 职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理 完成一件事务,附带的结果就是编程简洁清晰。

  • 高扩展性

具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱 如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

  • 智能化

以下的动态代理章节中你就会看到代理的智能化

代理模式的使用场景
为什么使用代理模式,类似于打游戏找代练,打官司找律师,盖房子找开发商。目的就是减轻自己的负担。

代理模式的扩展

普通代理
  • 普通代理要求客户端只能访问代理角色,而不能访问真实角色。
    代理模式(普通代理到动态代理)_第4张图片
  • GamePlayer 的构造函数增加了 IGamePlayer 参数,代理者只要传入代理者的名字即可
public class GamePlayer implements IGamePlayer {
    // 游戏账户名
    private String userName;

    public GamePlayer(IGamePlayer gamePlayer, String userName) throws Exception {
        if (gamePlayer == null) {
            throw new Exception("不能创建真实角色");
        } else {
            this.userName = userName;
        }
    }

    @Override
    public void login(String user, String pwd) {
        System.out.println("登录账号为:" + user + userName + "登录成功");
    }

    @Override
    public void killBoss() {
        System.out.println(this.userName + "打boss");
    }

    @Override
    public void upgrade() {
        System.out.println("升级了。。。");
    }
}


// 代理类
public class GamePlayerProxy implements IGamePlayer {

    private IGamePlayer gamePlayer;

    public GamePlayerProxy(String userName) {
        try {
            gamePlayer = new GamePlayer(this, userName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 代练登录
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user, pwd);
    }

    // 代练杀怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    // 代练升级
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}


public static void main(String[] args) {
        IGamePlayer proxy = new GamePlayerProxy("代理者");
        System.out.println("游戏开始");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("游戏结束");
    }

调用者只需要知道代理类就可以了。不用知道代理了谁。该模式下,屏蔽了真实角色变更对高层模块的影响。真实角色想怎么修改怎么修改。该模式非常适合要求扩展性比较高的场合。

强制代理
  • 强制代理要求必须通过真实角色找到代理角色
    代理模式(普通代理到动态代理)_第5张图片
public interface IGamePlayer {
    // 登录游戏
    void login(String user, String pwd);

    // 杀怪
    void killBoss();

    // 升级
    void upgrade();

    IGamePlayer getProxy();
}

public class GamePlayer implements IGamePlayer {
    // 游戏账户名
    private String userName;
    private IGamePlayer proxy = null;

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

    @Override
    public void login(String user, String pwd) {
        if (this.isProxy()) {
            System.out.println("登录账号为:" + user + userName + "登录成功");
        } else {
            System.out.println("请使用代理");
        }

    }

    @Override
    public void killBoss() {
        if (this.isProxy()) {
            System.out.println(this.userName + "打boss");
        } else {
            System.out.println("请使用代理");
        }
    }

    @Override
    public void upgrade() {
        if (this.isProxy()) {
            System.out.println("升级了。。。");
        } else {
            System.out.println("请使用代理");
        }
    }

    @Override
    public IGamePlayer getProxy() {
        this.proxy = new GamePlayerProxy(this);
        return this.proxy;
    }

    private boolean isProxy() {
        return this.proxy != null;
    }
}

public class GamePlayerProxy implements IGamePlayer {

    private IGamePlayer gamePlayer;

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

    // 代练登录
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user, pwd);
    }

    // 代练杀怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    // 代练升级
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }

    @Override
    public IGamePlayer getProxy() {
        return null;
    }
}

// 直接访问真实角色类
        IGamePlayer player = new GamePlayer("张飒");
        System.out.println("游戏开始");
        player.login("zhangsan", "password");
        player.killBoss();
        player.upgrade();
        System.out.println("游戏结束");

    
	游戏开始
	请使用代理
	请使用代理
	请使用代理
	游戏结束

// 直接访问代理角色类
        IGamePlayer player = new GamePlayer("张飒");
        IGamePlayer proxy = new GamePlayerProxy(player);
        System.out.println("游戏开始");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("游戏结束");

	游戏开始
	请使用代理
	请使用代理
	请使用代理
	游戏结束

强制代理使用

   		IGamePlayer player = new GamePlayer("张飒");
        IGamePlayer proxy = player.getProxy();
        System.out.println("游戏开始");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("游戏结束");

	游戏开始
	登录账号为:zhangsan张飒登录成功
	张飒打boss
	升级了。。。
	游戏结束
代理是有个性的
  • 一个类可以实现多个接口,完成不同任务的整合。代理类不仅可以实现主题类的接口,还可以实现其他接口完成不同的任务。代理的目的就是在目标方法基础上做增强,增强的本质就是对目标方法的拦截和过滤。比如代练游戏是要花钱的。
public interface IProxy {
    void count();
}
// 代理类
public class GamePlayerProxy implements IGamePlayer, IProxy {

    IGamePlayer gamePlayer;

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

    // 代练登录
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user, pwd);
    }

    // 代练杀怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    // 代练升级
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
        this.count();
    }

    @Override
    public void count() {
        System.out.println("升级的费用为100元");
    }
}



动态代理
  • 什么是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段 才指定代理哪一个对象。,现在有一个非常流行的名称叫做面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。

代理模式(普通代理到动态代理)_第6张图片

  • InvocationHandler 是JDK中提供的动态代理接口,对被代理类的方法进行代理。其中invoke方法是必须实现的,它完成真实方法的调用。
public class GamePlayerIH implements InvocationHandler {
    // 被代理者
    Class clazz = null;
    // 被代理实例
    Object object = null;

    public GamePlayerIH(Object object) {
        // 我要代理谁
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.object, args);
    }
}

  • 接下来是场景类,既没有实现IGamePlayer也没有创建代理类。这就是动态代理。
 public static void main(String[] args) {
        IGamePlayer gamePlayer = new GamePlayer("张三");
        InvocationHandler invocationHandler = new GamePlayerIH(gamePlayer);
        System.out.println("开始时间是 早上十点");
        ClassLoader cl = gamePlayer.getClass().getClassLoader();
        IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class},invocationHandler);
        proxy.login("张三","pwd");
        proxy.upgrade();
        proxy.killBoss();
        System.out.println("结束时间晚上十点");
    }
  • 如果想让用户登录后给自己一个消息提醒怎么做
public class GamePlayerIH implements InvocationHandler {
    // 被代理者
    Class clazz = null;
    // 被代理实例
    Object object = null;

    public GamePlayerIH(Object object) {
        // 我要代理谁
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equalsIgnoreCase("login")){
            System.out.println("有人在登录账号");
        }
        return method.invoke(this.object, args);
    }
}

这就是AOP编程。

  • 接下来我们看看动态代理的模型
    代理模式(普通代理到动态代理)_第7张图片

动态代理实现代理的职责,业务逻辑Subject实现相关逻辑功能。通知Advice从另一个切面切入,最终在Client进行耦合。完成封装任务。

/**
 * 抽象主题类
 */
public interface Subject {
    void doSomething(String str);
}
/**
 * 真实主题类
 */
public class RealSubject implements Subject {
    @Override
    public void doSomething(String str) {
        System.out.println("do Something - - - > " + str);
    }
}

/**
 * 动态代理类
 */
public class MyInvocationHandler implements InvocationHandler {
    // 被代理的实例
    private Object object;

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 执行被代理的方法
        return method.invoke(object,args);
    }
}
/**
 * 动态代理类
 * @param 
 */
public class DynamicProxy<T> {
    public static <T> T newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler) {
        if (true) {
            // 执行通知
            new BeforeAdvice().exec();
        }
        return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

下面是实现的简单AOP行为 Advice

/**
 * 通知接口
 */
public interface IAdvice {
    void exec();
}

/**
 * 通知实现类
 */
public class BeforeAdvice implements IAdvice {
    @Override
    public void exec() {
        System.out.println("我是前置通知。。。");
    }
}

业务调用

 public static void main(String[] args) {
        Subject subject = new RealSubject();
        InvocationHandler handler = new MyInvocationHandler(subject);
        // 此方法生成了一个新的对象,getInterfaces() 查找所有的方法 并让 handler 接管这些所有方法
        Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
        proxy.doSomething("Finish");
    }

动态调用过程如下:
代理模式(普通代理到动态代理)_第8张图片
上面代码还有扩展的余地–>封装具体业务的代理类

public class SubjectProxy extends DynamicProxy {
    public static <T> T newProxyInstance(Subject subject) {
        ClassLoader classLoader = subject.getClass().getClassLoader();
        Class<?>[] interfaces = subject.getClass().getInterfaces();
        InvocationHandler handler = new MyInvocationHandler(subject);
        return newProxyInstance(classLoader, interfaces, handler);
    }
}

实际调用会更简单

    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxy = SubjectProxy.newProxyInstance(subject);
        proxy.doSomething("Finish");
    }

你可能感兴趣的:(设计模式)