设计模式五:代理模式

定义

为其他对象提供一种代理难以控制对这个对象的访问。

通用代码

抽象主题类

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

真实主题类

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

代理类

public class Proxy implements Subject {
    // 要代理哪个实现类
    private Subject subject = null;
    // 默认被代理者
    public Proxy() {
        this.subject = new Prox();
    }
    // 通过构造函数传递代理者
    public Proxy(Object ...objects) {
    }
    // 实现接口中定义的方法
    public void request() {
        this.before();
        this.subject.request();
        this.after();
    }
    // 预处理
    private void before() {
        // do somethin
    }
    // 善后处理
    private void after() {
        // do something
    }
}

before和after方法,能够引出一个崭新的编程方式,AOP(Aspect Oriented Programming 面向横切面编程)

优点

  1. 职责清晰。真实的角色就是实现实际的业务逻辑,不用关系其他非本职责事物,通过后期的代理完成一件事务,结果是编程简洁清晰。
  2. 高扩展性。具体主题角色是随时都会发生改变,只要实现了接口,不管怎么变化,代理类在不需要修改的情况下使用。
  3. 智能化。这里没有体现出来,可以查看Struts是如何把表单元素映射到对象上。

使用场景

为什么要用代理?对应现实世界,为什么打官司要找律师(代理类)?因为我们(真实主题类)不想参与中间过程的是是非非,我们(真实主题类)只要完成自己的答辩,其他如事前调查,事后追查都有律师(代理类)完成,这就减轻了我们(真实主题类)的负担。代理模式使用的场景非常多,Spring AOP就是非常典型的动态代理。
代理类可以为真实角色预处理消息,过滤消息,消息转发,事后处理消息等功能。

扩展

强制代理

必须通过真实角色查找到代理角色,否则不能访问。只有通过真实角色指定的代理类才能访问,也就是真实角色管理代理角色。高层模块new一个真实角色的对象,返回的却是代理角色。举例子:
我和一个明星比较熟,带电话为她
“沙比呀,我要见XXX导演,你帮下忙!”
“不行啊,我这几天很忙,你找我的经纪人吧……”
我想绕过她的经纪人,结果她返回了她的代理,这就是强制代理。

强制代理的接口类

public interface IGamePlayer {
    // 登录游戏
    public void login(String user, String password);
    // 每个人都可以找一个自己的打理
    public IGamePlayer getProxy();
}

强制代理的真是角色

public class GamePlayer implements IGamePlayer {
  private String name = "";
  //我的代理是谁
  private IGamePlayer proxy = null;
  public GamePlayer(String _name) {
    this.name = _name;
  }
  // 找到自己的代理
  public IGamePlayer getProxy() {
    this.proxy = new GamePlayerProxy(this.name);
    return this.proxy;
  }
  // 登录
  public void login(String user, String password) {
    if (this.isProxy()) {
      System.out.println("登录成功");
    } else {
      System.out.println("请使用指定的代理");
    }
  }
  // 检查是否是代理访问
  private boolean isProxy() {
    if (this.proxy == null) return false;
    return true;
  }
}

强制代理的代理类

public class GamePlayerProxy implements IGamePlayer {
  private IGamePlayer gamePlayer = null;
  // 构造函数传递用户名
  public GamePlayerProxy(IGamePlayer _gamePlayer) {
    this.gamePlayer = _gamePlayer;
  }
  public void login(String user, String password) {
    this.gamePlayer.login(user, password);
  }
  // 代理的代理没有,就是自己
  public IGamePlayer getProxy() {
    return this;
  }
}

场景类

public class Clinet {
  public static void main(String[] args) {
    // 定义一个游戏角色
    IGamePlayer player = new GamePlayer("张三");
    // 获取指定代理
    IGamePlayer proxy = player.getProxy();
    proxy.login();
  }
}

要从真实角色找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy可以访问真实角色的所有方法,代理的管理已经由真实角色自己完成。

虚拟代理

虚拟代理(Vriual Proxy)听起来复杂,其实很简单
通用代码

public class Proxy implements Subject {
  // 要代理哪个实现类
  private Subject subject = null;
  // 实现接口中定义的方法
  public void request() {
    // 判断真实角色是否存在
    if (subject == null) {
      subject = new RealSubject();
    }
    this.subject.request();
  }
}

在需要的时候才初始化真实角色,避免被代理对象较多而引起的初始化缓慢的问题。缺点是需要在每个方法中判断主题对象是否被创建。

动态代理

这才是压轴大戏
动态代理:在实现阶段不用关心代理谁,而在运行阶段才确定代理哪一个对象。相对来说,自己写代理类就是静态代理。

通用类图

通用类图

两条独立发展的路线,两者没有必然相互耦合的关系:
- 动态代理实现代理职责
- 业务逻辑Subject实现相关的逻辑功能
通知Advice从另一个切面切入,最终在高层模块Client进行耦合,完成逻辑封装任务。

通用代码

抽象主题

public interface Subject {
  // 业务操作
  public void doSomething(String str);
}

真实主题

public class RealSubject implements Subject {
  public void doSomething(String str) {
    // 业务操作
    System.out.println("do something!---->" + str);
  }
}

重点是 动态代理的Handler类

public class MyInvocationHandler implements InvocationHandler {
  // 被代理对象
  private Object target = null;
  // 通过构造函数传递一个对象
  public MyInvocationHandler(Object _obj) {
    this.target = _obj;
  }
  // 代理方法
  public Object invoke(Object proxy, Method methd, Object[] args) \
    throws Throwable {
      // 执行被代理的方法
      return method.invoke(this.target, args);
  }
}

invoke方法是InvocationHandler接口必须实现的,它完成对真实方法的调用。给定一个接口,动态代理宣称“我已经实现了该接口下的所有方法”,实际上所有方法返回值都是空的,没有业务逻辑,需要通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。
invoke方法中,proxy一般是不用的,因为调用任何proxy的方法都会被转到invoke方法实际执行,因此在invoke中用proxy的方法将导致无限循环

动态代理类

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);
  }
}

对 Proxy.newProxyInstance做了一层封装,目的是暴露出横切面,这样切入类Advice可以单独编写,实现解耦。
Proxy.newProxyInstance。是一个无中生有的对象,需要
1. 定义该对象由那个加载器加载,即该对象是什么类型的
2. 定义对象由哪些方法
3. 定义调用这些方法时实际由谁去执行
Proxy.newProxyInstance的三个参数:
- loader: 代理对象由哪一个加载器加载
- interfaces: 代理对象的类型,即其中有哪些方法
- h:当调用代理对象其中的方法,该执行的代码

动态代理调用过程示意图

通知接口及实现

public interface IAdvice {
  // 通知只有一个方法,执行即可
  public void exec();
}
public class BeforeAdvice implements IAdvice {
  public void exec() {
    System.out.println("我是前置通知,我被执行了");
  }
}

场景类

public class Clinet {
  public static void main(String[] args) {
    // 
    Subject subject = new RealSubject();
    InvocationHandler handler = new MyInvocationHandler();
    Subject proxy = DynamicProxy.newProxyInstance(\ 
             subject.getClass().getClassLoader(), \
             subject.getClass().getInterfaces(), handler);
    proxy.doSomething("Finish");

  }
}

运行结果

我是前置通知,我被执行了
do something!---->Finish

动态代理和静态代理有什么区别?都是需要实现一个代理类,有区别,看父类,动态代理的主要意图就是解决我们常说的“审计”问题,也就是横切面编程,在不改变我们已有代码结构的情况下增强或控制对象的行为。

参考

《设计模式之禅》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值