代理模式Proxy

243 阅读8分钟

代理模式Proxy

代理模式(Proxy)为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。

代理模式的作用

  • 在某些情况下,一个客户类不想或者不能直接引用一个对象,而代理类对对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 代理类除了是客户类和委托类的中介之外,我们还可以对被代理类做增强(通过给代理类增加额外的功能来扩展委托类的功能),我们这样做只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。

举个栗子

现在有一个游戏工厂接口GameFactory,抽象了一个制造游戏机的方法make()

/**
 * 游戏工厂类
 */
public interface GameFactory {
    /**
     * 制造游戏机
     */
    void make();
}

这个接口有两个实现类:制造Ps4的工厂:Ps4Factory,和制造XBox的工厂XBoxFactory

/**
 * 功能描述:Ps4工厂
 */
public class Ps4Factory implements GameFactory {
    @Override
    public void make() {
        System.out.println("-生产了一个Ps4游戏机-");
    }
}
/**
 * 功能描述:XBox工厂
 *
 * @author abner
 * @version 1.0
 * @since 2020/6/9 20:27
 */
public class XBoxFactory implements GameFactory {
    @Override
    public void make() {
        System.out.println("-生产了一个XBox游戏机-");
    }
}

so,如果消费者想买游戏机,可以直接找对应的游戏机生产厂商购买,像这样:

/**
 * 功能描述:消费者
 */
public class Consumer {

    public static void main(String[] args) {
        //直接找不同的游戏机工厂买游戏机
        Ps4Factory ps4Factory = new Ps4Factory();
        ps4Factory.make();

        XBoxFactory xBoxFactory = new XBoxFactory();
        xBoxFactory.make();
    }
}

打印结果:

-生产了一个Ps4游戏机-
-生产了一个XBox游戏机-

but,有时候,消费者没有办法直接联系到厂商,比如国内访问不了国外购物网站,这个时候可以委托代购去购买(代理)。

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类。

使用静态代理购买游戏机:

创建一个静态代理:游戏机代理商,和代理类一样也实现接口的方法

/**
 * 功能描述:静态代理:游戏机代理商
 */
public class StaticProxy implements GameFactory {

    private GameFactory gameFactory;

    /**
     * 构造方法:传入被代理对象GameFactory
     * @param gameFactory 被代理对象GameFactory
     */
    public StaticProxy(GameFactory gameFactory) {
        this.gameFactory = gameFactory;
    }


    @Override
    public void make() {
        //前置增强
        doSomeThingBefore();

        //执行被代理类的方法
        gameFactory.make();

        //后置增强
        doSomeThingAfter();
    }

    private void doSomeThingBefore() {
        System.out.println("在方法执行之前做点什么");
    }

    private void doSomeThingAfter() {
        System.out.println("在方法执行之后做点什么");
    }
}

so,现在可以使用静态代理去买游戏机了:

/**
 * 功能描述:消费者
 */
public class Consumer {

    public static void main(String[] args) {
        //找静态代理买游戏机
        StaticProxy staticPs4Proxy = new StaticProxy(new Ps4Factory());
        staticPs4Proxy.make();

        StaticProxy staticXBoxProxy = new StaticProxy(new XBoxFactory());
        staticXBoxProxy.make();

    }
}

打印结果:

在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么

在方法执行之前做点什么
-生产了一个XBox游戏机-
在方法执行之后做点什么

静态代理总结

  • 优点:不修改目标对象的功能前提下,对目标功能进行扩展。
  • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类被创建,类太多;有时候接口会增加其它方法,但代理类也必须去实现它,哪怕对代理类本身没有任何作用。同时,一旦接口增加方法,目标对象和代理对象都要维护,增加维护成本。

动态代理

代理类在程序运行时创建的代理方式被称为动态代理,这种情况下,代理类并不是在Java代码中事先定义好的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

使用静态代理购买游戏机:

创建一个动态代理,实现InvocationHandler接口。

  • InvocationHandler接口定义了标准化的代理流程

动态代理给我们提供了2个东西:

  • proxy:生成代理对象
  • InvocationHandler:标准化的代理流程
/**
 * 功能描述:动态代理:
 */
public class DynamicProxy implements InvocationHandler {

    /**
     * 被代理对象
     *
     * 在运行时动态传入进来,传给我什么对象,就代理什么对象
     * 而不是像静态代理一样,运行前指定了代理的对象类型,只能代理那一种或几种类型的对象
     */
    private Object target;

    /**
     * target的Get方法
     * @return 被代理对象
     */
    public Object getTarget() {
        return target;
    }

    /**
     * target的Set方法
     * @param target 被代理对象
     */
    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     * 生成代理类的实例-代理对象
     * @return 代理对象
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前置增强
        doSomeThingBefore();

        //执行被代理类的方法
        Object o = method.invoke(target, args);

        //后置增强
        doSomeThingAfter();
        return null;
    }


    private void doSomeThingBefore() {
        System.out.println("在方法执行之前做点什么");
    }

    private void doSomeThingAfter() {
        System.out.println("在方法执行之后做点什么");
    }
}

使用动态代理买游戏机:

/**
 * 功能描述:消费者
 */
public class Consumer {

    public static void main(String[] args) {
        //找动态代理买游戏机
        DynamicProxy dynamicProxy = new DynamicProxy();
        //1.设置被代理的类:Ps4工厂
        dynamicProxy.setTarget(new Ps4Factory());
        //2.获取代理类的实例
        GameFactory ps4Factory1 = (GameFactory) dynamicProxy.getProxy();
        ps4Factory1.make();
    }
}

打印结果:

在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么

代理服务于多种类型

在上面,都是只代理了一个接口GameFactory,但现在我又创建了一个接口BookFactory,它里面只有一个印刷书本的抽象方法print()

/**
 * 书本工厂类
 */
public interface BookFactory {
    /**
     * 印刷书本
     */
    void print();
}

同样,BookFactory也有一个实现类:

/**
 * 功能描述:科学类书本印刷厂
 */
public class SinceBookFactory implements BookFactory {
    @Override
    public void print() {
        System.out.println("印刷了一本科学书籍...");
    }
}

如果我的代理类要同时代理这两个接口,该怎么办呢?

  • 静态代理需要改成这样:

再实现一个BookFactory接口,并重写它的print()方法

/**
 * 功能描述:静态代理:游戏机代理商&书本代理商
 */
public class StaticProxy implements GameFactory, BookFactory {

    private GameFactory gameFactory;

    private BookFactory bookFactory;

    /**
     * 构造方法:传入被代理对象GameFactory
     * @param gameFactory 被代理对象GameFactory
     */
    public StaticProxy(GameFactory gameFactory) {
        this.gameFactory = gameFactory;
    }

    /**
     * 构造方法:传入被代理对象BookFactory
     * @param bookFactory 被代理对象BookFactory
     */
    public StaticProxy(BookFactory bookFactory) {
        this.bookFactory = bookFactory;
    }

    @Override
    public void make() {
        //前置增强
        doSomeThingBefore();

        //执行被代理类的方法
        gameFactory.make();

        //后置增强
        doSomeThingAfter();
    }

    @Override
    public void print() {
        //前置增强
        doSomeThingBefore();

        //执行被代理类的方法
        bookFactory.print();

        //后置增强
        doSomeThingAfter();
    }

    private void doSomeThingBefore() {
        System.out.println("在方法执行之前做点什么");
    }

    private void doSomeThingAfter() {
        System.out.println("在方法执行之后做点什么");
    }
}

使用静态代理买书:

/**
 * 功能描述:消费者
 */
public class Consumer {

    public static void main(String[] args) {
        //找静态代理买游戏机
        StaticProxy staticPs4Proxy = new StaticProxy(new Ps4Factory());
        staticPs4Proxy.make();

        StaticProxy staticXBoxProxy = new StaticProxy(new XBoxFactory());
        staticXBoxProxy.make();

        //找静态代理买书
        StaticProxy staticBookProxy = new StaticProxy(new ScienceBookFactory());
        staticBookProxy.print();
    }
}

打印结果:

在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么

在方法执行之前做点什么
-生产了一个XBox游戏机-
在方法执行之后做点什么

在方法执行之前做点什么
印刷了一本科学书籍...
在方法执行之后做点什么

  • 对于动态代理:

由于动态代理的代理对象是在运行时动态创建, 所以我们可以代理任何类型的对象,只要把被代理的对象传给它就行了。

现在,不需要改任何代码,除了游戏机,我还可以向动态代理买书了!

​ 使用动态代理买书:

/**
 * 功能描述:消费者
 */
public class Consumer {

    public static void main(String[] args) {
        //找动态代理买游戏机
        DynamicProxy dynamicProxy = new DynamicProxy();
        //1.设置被代理的类:Ps4工厂
        dynamicProxy.setTarget(new Ps4Factory());
        //2.获取代理类的实例
        GameFactory ps4Factory1 = (GameFactory) dynamicProxy.getProxy();
        ps4Factory1.make();

        //找动态代理买书
        //1.设置被代理的类:科学类书印刷厂
        dynamicProxy.setTarget(new SinceBookFactory());
        //2.获取代理类的实例
        BookFactory bookFactory = (BookFactory) dynamicProxy.getProxy();
        bookFactory.print();
    }
}

打印结果:

在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么

在方法执行之前做点什么
印刷了一本科学书籍...
在方法执行之后做点什么

总结

动态代理VS静态代理

  • 静态代理类和委托类实现了相同的接口,代理类通过委托实现了相同的方法。这样就出现了大量的代码重复。
  • 静态代理对象只服务于一种类型的对象,如果要服务多类型的对象,势必要为每一种对象都进行代理(implement interface1,interface2...)。静态代理在程序规模稍大时就无法胜任了。