静态代理

UML图

image.png

问题引入

  • 要获取坦克的运行时间
  • move方法为Movable接口中的方法
  • Tank实现了Movable接口并重写了move方法
  • 问题:如何在不改变move内部逻辑(即无法改变方法源码时)的情况下获取其运行时间? ```java package com.mashibing.dp.proxy.v01;

import java.util.Random;

/**

  • 问题:我想记录坦克的移动时间
  • 简单的办法:修改代码,记录时间 */ public class Tank implements Movable {

    /**

    • 模拟坦克移动了一段儿时间 */ @Override public void move() {

      // long start = System.currentTimeMillis();

      System.out.println(“Tank moving claclacla…”); try {

      1. Thread.sleep(new Random().nextInt(10000));

      } catch (InterruptedException e) {

      1. e.printStackTrace();

      }

      // long end = System.currentTimeMillis(); // System.out.println(end - start);

      } }

interface Movable { void move(); }

  1. - benchmark:性能测试,找到性能最差的点(可能是整个程序中的某个方法);但我们是拿不到源码
  2. - 方案1:用继承,调用super.move(); 代码如下===>这种方法不好(慎用继承,因为耦合度太高)
  3. ```java
  4. package com.mashibing.dp.proxy.v04;
  5. import java.util.Random;
  6. /**
  7. * 问题:我想记录坦克的移动时间
  8. * 最简单的办法:修改代码,记录时间
  9. * 问题2:如果无法改变方法源码呢?
  10. * 用继承?
  11. */
  12. public class Tank implements Movable {
  13. /**
  14. * 模拟坦克移动了一段儿时间
  15. */
  16. @Override
  17. public void move() {
  18. System.out.println("Tank moving claclacla...");
  19. try {
  20. Thread.sleep(new Random().nextInt(10000));
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. public static void main(String[] args) {
  26. new Tank2().move();
  27. }
  28. }
  29. // 继承Tank重写move,并在其中通过super调用父类的move
  30. class Tank2 extends Tank {
  31. @Override
  32. public void move() {
  33. long start = System.currentTimeMillis();
  34. super.move();
  35. long end = System.currentTimeMillis();
  36. System.out.println(end - start);
  37. }
  38. }
  39. interface Movable {
  40. void move();
  41. }
  • 方案2:在main中测试(不好,因为对于有很多方法的大程序来说依然不方便)
  • 方案3:用聚合(静态代理,将tank对象聚合到中间代理人TankProxy中去)
    • 代理:买茅台,不直接去厂商买,而是代理人去厂商买,加价后再卖给我们消费者
    • 代理人帮忙多做了一点事,也做了一点自己的事,但是做的都是一件事,所以实现的都是同一个接口Movable(代理茅台,不能变成五粮液) ```java package com.mashibing.dp.proxy.v05;

import java.util.Random;

/**

  • 问题:我想记录坦克的移动时间
  • 最简单的办法:修改代码,记录时间
  • 问题2:如果无法改变方法源码呢?
  • 用继承?
  • v05:使用代理
  • 代理有各种类型:时间time、日志log */ public class Tank implements Movable {

    /**

    • 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println(“Tank moving claclacla…”); try {

      1. Thread.sleep(new Random().nextInt(10000));

      } catch (InterruptedException e) {

      1. e.printStackTrace();

      } }

      public static void main(String[] args) { new TankTimeProxy(new Tank()).move(); new TankLogProxy(new Tank()).move(); } }

// 用聚合代替继承===>解耦 // 记录时间 class TankTimeProxy implements Movable {

  1. Tank tank;
  2. // 传入tank对象
  3. public TankTimeProxy(Tank tank) {
  4. this.tank = tank;
  5. }
  6. @Override
  7. public void move() {
  8. long start = System.currentTimeMillis();
  9. tank.move();
  10. long end = System.currentTimeMillis();
  11. System.out.println(end - start);
  12. }

}

// 记录日志 class TankLogProxy implements Movable { Tank tank; @Override public void move() { System.out.println(“start moving…”); tank.move(); System.out.println(“stopped!”); } }

interface Movable { void move(); }

  1. <a name="MXGQu"></a>
  2. ## 将代理对象改成Movable类型(聚合对象改为接口类型)
  3. - 静态代理
  4. - 越来越像Decorator模式
  5. - 实现代理的各种组合(装饰器模式、cor责任链?)
  6. - 继承的组合无穷无尽,但是组合通过实现接口可以一个套一个,方便一些
  7. - 即一个代理对另一个代理进行代理
  8. - **代理的代理,多层代理**
  9. - 设计模式学到后面就是多态的简单运用了(融会贯通、相互融合)
  10. - 只是语义上有区分,本质上用的都是各种各样的多态
  11. ```java
  12. package com.mashibing.dp.proxy.v07;
  13. import java.util.Random;
  14. /**
  15. * 问题:我想记录坦克的移动时间
  16. * 最简单的办法:修改代码,记录时间
  17. * 问题2:如果无法改变方法源码呢?
  18. * 用继承?
  19. * v05:使用代理
  20. * v06:代理有各种类型
  21. * 问题:如何实现代理的各种组合?继承?Decorator?
  22. * v07:代理的对象改成Movable类型-越来越像decorator了
  23. *
  24. */
  25. public class Tank implements Movable {
  26. /**
  27. * 模拟坦克移动了一段儿时间
  28. */
  29. @Override
  30. public void move() {
  31. System.out.println("Tank moving claclacla...");
  32. try {
  33. Thread.sleep(new Random().nextInt(10000));
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. public static void main(String[] args) {
  39. Tank t = new Tank();
  40. TankTimeProxy ttp = new TankTimeProxy(t);
  41. TankLogProxy tlp = new TankLogProxy(ttp);
  42. tlp.move();
  43. // new TankLogProxy(
  44. // new TankTimeProxy(
  45. // new Tank()
  46. // )
  47. // ).move();
  48. }
  49. }
  50. class TankTimeProxy implements Movable {
  51. Movable m;
  52. public TankTimeProxy(Movable m) {
  53. this.m = m;
  54. }
  55. @Override
  56. public void move() {
  57. long start = System.currentTimeMillis();
  58. m.move();
  59. long end = System.currentTimeMillis();
  60. System.out.println(end - start);
  61. }
  62. }
  63. class TankLogProxy implements Movable {
  64. Movable m;
  65. public TankLogProxy(Movable m) {
  66. this.m = m;
  67. }
  68. @Override
  69. public void move() {
  70. System.out.println("start moving...");
  71. m.move();
  72. long end = System.currentTimeMillis();
  73. System.out.println("stopped!");
  74. }
  75. }
  76. interface Movable {
  77. void move();
  78. }

需求升级—->静态代理的不足

  • 如果有stop方法需要代理…===>需要修改的太多(改接口、该实现、改代理类)===>需要新增方法这就要修改代码了,违反了开闭原则
  • 让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 ===> Object
  • 日志记录===>时间计算是很多方法都需要的东西
  • 不能直接将Movable接口类型改为Object类型,因为使用接口的目的就是为了将代理方法和被代理方法对应起来,将接口改为Object之后,调用原方法不光要进行强制类型转换,而且不容易看出关系
  • 解决方案: 详见下一篇
    • 分离代理行为与被代理对象
    • 使用jdk的动态代理
    • cglib的动态代理