美文网首页
设计模式 -- 代理模式 (Proxy Pattern)

设计模式 -- 代理模式 (Proxy Pattern)

作者: 小杰的快乐时光 | 来源:发表于2018-08-18 20:33 被阅读0次

代理模式也叫委托模式
定义:为其他对象提供一种代理以控制对这个对象的访问。

代理模式通用的类图如下:

代理模式 .png

我们来分析类图中的几个角色定义:
①Subject 抽象主题角色:可以是抽象类,也可以是接口,是一个最普通的业务类型定义。
②RealSubject 真实主题角色:也叫做被委托角色,被代理角色,是Subject 抽象主题角色的具体实现者,业务逻辑具体执行者
③Proxy 代理主题角色:也叫做委托类,代理类。负责对真实角色的应用,把所有抽象主题委托的方法限制给真实主题角色实现,并且在真实主题角色处理完毕前后做一些预处理和善后工作。

通用代码实现
Subject 抽象主题角色

public interface Subject {
   public void request();
}

RealSubject 真实主题角色

public class RealSubject implements Subject {
   @Override
   public void request() {
      //业务处理
   }
}

Proxy 代理主题角色:

public class Proxy implements Subject {
   private Subject subject = null;
   
   public Proxy(Subject subject) { //通过构造函数指定传递被代理者
      this.subject = subject;
   }
   
   @Override
   public void request() {
      this.Before();
      this.subject.request();
      this.After();
   }

  //预处理
   private void Before(){
      //代理前业务处理
   }

    //善后处理
   private void After(){
      //代理后业务处理
   }
}

一个代理类可以代理多个被委托者,因此我们需要在高层模块中自己指定代理类具体代理哪个真实主题角色,也就是代码中所写的,通过构造函数传递被代理者。

代理模式的优点
①职责清晰:真实的角色就是实现实际的业务逻辑,不用关系其他非本职责的事物。
②高扩展性:真实主题角色是随时变化的,不管它怎么变,只要是实现了接口,都能被在不修改代理类下使用
③智能化:动态代理

场景模拟
使用代理模式实现代练公司给玩家代练游戏账号
玩家接口(抽象主题角色):定义登录,杀怪,升级 三种行为

public interface IGamePlayer {
   //登录账户名与密码
   public void login(String username,String password);
   //杀怪
   public void killBoss();
   //升级
   public void upgrade();
}

玩家实现类(真实主题角色)

public class GamePlayer implements IGamePlayer{
   private String name = "";
   
   public GamePlayer(String name) {
      this.name = name;
   }
   
   @Override
   public void login(String username, String password) {
      System.out.println("登录名为:"+username+" 密码为:"+password+" 登录成功");
   }
   
   @Override
   public void killBoss() {
      System.out.println(this.name+" 在打boss");
   }
   
   @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;
   }
   
   @Override
   public void login(String username, String password) {
      this.gamePlayer.login(username,password);
   }
   
   @Override
   public void killBoss() {
      this.gamePlayer.killBoss();
   }
   
   @Override
   public void upgrade() {
      this.gamePlayer.upgrade();
   }
}

客户端

public class Client {
   public static void main(String[] args) {
      System.out.println("---------未使用代理模式----------");
      IGamePlayer player = new GamePlayer("张三");
      player.login("zhangsan","123456");
      player.killBoss();
      player.upgrade();
      
      System.out.println("---------代理模式----------");
      GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(player);
      gamePlayerProxy.login("张三","123456");
      gamePlayerProxy.killBoss();
      gamePlayerProxy.upgrade();
   }
}

代理模式的扩展:
类似于网络代理服务器设置,代理模式也分为:普通代理与强制代理
(1)普通代理:用户必须知道代理的存在,通过代理寻找真实角色,也就是Proxy 代理主题角色这个类的存在是对用户透明的。普通代理的要求是客户端只能访问代理角色,而不能访问RealSubject 真实主题角色。那么使用普通代理改造上述场景模拟,代理类与玩家实现类修改如下
玩家实现类

public class GamePlayer implements IGamePlayer{
   private String name = "";
   //通过构造函数确认谁能创建真实对象
   public GamePlayer(IGamePlayer gamePlayer,String name) {
      if (gamePlayer == null){
         throw new RuntimeException("不能创建真实角色");
      }else {
         this.name = name;
      }
   }
   /.....以下省略...../

代理类

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通过构造函数传递对谁进行代练
   public GamePlayerProxy(String username) {
      try {
         //将代理类传递过去是为了检查谁能创建真实角色
         this.gamePlayer = new GamePlayer(this,username);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   /.....以下省略...../

客户端

public class Client {
   public static void main(String[] args) {
      System.out.println("---------使用普通代理模式----------");
      IGamePlayer player = new GamePlayerProxy("张三");
      player.login("zhangsan","123456");
      player.killBoss();
      player.upgrade();
   }
}

经过如上代码修改,我们不需要知道真实角色是谁,屏蔽真实角色对高层模块的影响,扩展性强。

(2)强制代理:与普通代理相反,强制代理是通过真实角色寻找代理,否则无法访问(无论是通过代理还是直接new一个主题类对象)。那么使用强制代理改造上述场景模拟,代码修改如下

既然是通过真实角色获取代理对象,那么玩家类就需要修改
玩家接口修改

public interface IGamePlayer {
   //登录账户名与密码
   public void login(String username,String password);
   //杀怪
   public void killBoss();
   //升级
   public void upgrade();
   //获取代理类
   public IGamePlayer getProxy();
}

玩家实现类修改

public class GamePlayer implements IGamePlayer{
   private String name = "";
   private IGamePlayer proxy = null;
   //通过构造函数确认谁能创建真实对象
   public GamePlayer(String name) {
      this.name = name;
   }
   
   @Override
   public void login(String username, String password) {
      if (this.isProxy()){
         System.out.println("登录名为:"+username+" 密码为:"+password+" 登录成功");
      }else {
         System.out.println("请使用指定的代理访问");
      }
   }
   
   @Override
   public void killBoss() {
      if(this.isProxy()){
         System.out.println(this.name+" 在打boss");
      }else {
         System.out.println("请使用指定的代理访问");
      }
   }
   
   @Override
   public void upgrade() {
      if (this.isProxy()){
         System.out.println(this.name+ " 升级了!");
      }else {
         System.out.println("请使用指定的代理访问");
      }
   }
   
   @Override
   public IGamePlayer getProxy() {
      //指定代理对像
      this.proxy = new GamePlayerProxy(this);
      return this.proxy;
   }
   
   //检测是否是代理访问
   private boolean isProxy(){
      if (this.proxy == null){
         return false;
      }else {
         return true;
      }
   }
}

代理类修改

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通过构造函数传递对谁进行代练
   public GamePlayerProxy(IGamePlayer gamePlayer) {
      this.gamePlayer = gamePlayer;
   }
   
   @Override
   public void login(String username, String password) {
      this.gamePlayer.login(username,password);
   }
   
   @Override
   public void killBoss() {
      this.gamePlayer.killBoss();
   }
   
   @Override
   public void upgrade() {
      this.gamePlayer.upgrade();
   }
   
   @Override
   public IGamePlayer getProxy() {
      //代理的代理没有,默认返回自己
      return this;
   }
}

客户端:

public class Client {
   public static void main(String[] args) {
      System.out.println("---------直接访问真实角色----------");
      IGamePlayer player1 = new GamePlayer("张三");
      player1.login("zhangsan","123456");
      player1.killBoss();
      player1.upgrade();
      
      System.out.println("---------直接访问真实角色----------");
      IGamePlayer player2 = new GamePlayer("张三");
      IGamePlayer proxy = new GamePlayerProxy(player2);
      proxy.login("zhangsan","123456");
      proxy.killBoss();
      proxy.upgrade();
      
      System.out.println("---------使用强制代理模式----------");
      IGamePlayer player3 = new GamePlayer("张三");
      IGamePlayer proxy3 = player3.getProxy();
      proxy3.login("zhangsan","123456");
      proxy3.killBoss();
      proxy3.upgrade();
   }
}
-------------output--------------
---------直接访问真实角色----------
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
---------直接访问真实角色----------
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
---------使用强制代理模式----------
登录名为:zhangsan 密码为:123456 登录成功
张三 在打boss
张三 升级了!

经过上面代码的演示,可以看出强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。 在客户端中使用getProxy方法就可以取得真实角色的代理,然后访问真是角色的所有方法。

动态代理
定义:在实现阶段不需要知道代理谁,在运行阶段才指定代理对象。
使用JDK提供的动态代理接口:java.lang.reflect.InvocationHandler,对被代理类的方法进行处理。
具体代码如下:

public class GamePlayIH implements InvocationHandler {
   //被代理者类
   Class cls = null;
   //被代理的实例对象
   Object obj = null;
   //通过构造函数传递被代理的实例对象
   public GamePlayIH(Object obj) {
      this.obj = obj;
   }
   //调用被代理的方法,invoke方法必须重写,它完成对真实方法的调用
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object result = method.invoke(this.obj,args);
      return result;
   }
}

动态代理是根据被代理的接口获取所有的方法,也就是给定一个接口,动态代理就会自动实现该接口下的所有方法,默认情况下所有方法返回值都为空值,也就是虽然动态代理虽然实现接口下的所有方法,但是并没有包含任何逻辑,因此还需要通过InvocationHandler接口来处理所有方法的实际任务。

客户端

public class Client {
   public static void main(String[] args) {
      //定义一个玩家
      IGamePlayer player = new GamePlayer("张三");
      //定义一个handler
      InvocationHandler handler = new GamePlayIH(player);
      //获取类加载器
      ClassLoader classLoader = player.getClass().getClassLoader();
      //在这里给定IGamePlayer接口与InvocationHandler接口,通过Proxy动态产生一个代理者
      IGamePlayer gamePlayerProxy = (IGamePlayer) Proxy.newProxyInstance(classLoader,new Class[]{IGamePlayer.class},handler);
      gamePlayerProxy.login("zhangsan","123456");
      gamePlayerProxy.killBoss();
      gamePlayerProxy.upgrade();
   }
}

让我们来看看动态代理的模型:动态代理通用类图

动态代理通用类图.png

根据通用类图,我们来写出通用的模式代码

public interface Subject {
   public void doSomething(String str);
}

public class RealSubject implements Subject {
   @Override
   public void doSomething(String str) {
      System.out.println("doSomething ---> "+str);
   }
}

public class MyInvocationHandler implements InvocationHandler {
   //被代理的实例对象
   Object obj = null;
   //通过构造函数传递被代理的实例对象
   public MyInvocationHandler(Object obj) {
      this.obj = obj;
   }
   //调用被代理的方法
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object result = method.invoke(this.obj,args);
      return result;
   }
}

public interface IAdvice {
   //通知只有一个执行方法
   public void exec();
}

public class BeforeAdvice implements IAdvice {
   @Override
   public void exec() {
      System.out.println("前置通知执行!");
   }
}

public class DynamicProxy<T> {
   public static <T> T newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h){
      //寻找JoinPoint连接点,AOP框架使用元数据定义
      if (true){
         //执行一个前置通知
         new BeforeAdvice().exec();
      }
      //执行目标,并返回结果
      return (T)Proxy.newProxyInstance(loader,interfaces,h);
   }
}

客户端

public class Client {
   public static void main(String[] args) {
      //定义一个真实主题
      Subject subject = new RealSubject();
      //定义一个代理handler
      InvocationHandler handler = new MyInvocationHandler(subject);
      //定义主题的代理
      Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
      //执行代理的行为
      proxy.doSomething("zhangsan");
   }
}

在这里注意看:

DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);

这段代码。subject.getClass().getClassLoader():获取subject对象的类加载器,subject.getClass().getInterfaces()是获取Subject类的所有接口,最后指定代理handler后,会实现该类的所有方法,由MyInvocationHandler类中的invoke方法接管所有的方法实现。

因此proxy.doSomething("zhangsan")这段代码的调用过程为:Client类 --> DynamicProxy代理类 --> InvocationHandler接口 --> MyInvocationHandler 实现类 --> 接管Subject接口的所有方法 --> RealSubject实现类

动态代理与静态代理的区别:动态代理是面向切面编程,在不改变我们已有的代码结构下增强或控制对象的行为。

注意:实现动态代理的首要条件是:被代理类必须实现一个接口,当然在CGLIB等技术上不需要接口也可以实现。

参考书籍:设计模式之禅 --- 秦小波 著

相关文章

网友评论

      本文标题:设计模式 -- 代理模式 (Proxy Pattern)

      本文链接:https://www.haomeiwen.com/subject/euxqiftx.html