设计模式之代理模式

前言

在我们平时的生活中代理的概念很多,大家都知道,谷歌在国内是被禁止访问的,但是如果我们使用VPN就可以访问了。当你想买个新车需要把旧的车卖掉时,一般你不会自己去卖,因为这很耗费自己的时间,所以你会找二手车交易市场,只需要给人家说出你的要求,具体售卖就不需要你来处理。当你想租房,买房或者卖房一般是会去房产中介。又或者你在外卖平台订餐等等。而我们提到的VPN,二手车交易市场,房产中介,外卖平台就充当了代理的角色。它能够代替”别人”进行工作,当然有什么事情直接找代理处理即可,如果代理处理不了才会找被代理对象处理。
在图解设计模式中,通过“带名字的打印机”程序介绍代理模式,当然打印机就是简单的打印输出字符信息。照例通过UML类图了解程序的大致框架。

这里写图片描述
首先我们我们给实例赋予名字Alice并显示该名字,然后将实例名字改为Bob并显示。在设置名字时都不会生成Printer类实例(即本人或者说被代理对象),而是有代理处理,当调用代理类的print方法时代理才会生成被代理对象并调用其print进入实际打印输出。

主体IPrintable

主体(Subject)角色IPrintable定义了代理(Proxy)角色PrinterProxy和实际的主体(RealSubject)角色Printer之间具有一致性的接口,由于存在IPrintable,所以使用者不必在乎究竟是用的代理PrinterProxy还是实际的主体Printer。

public interface IPrintable {
    public abstract void setPrinterName(String name);
    public abstract String getPrinterName();
    public abstract void print(String string);
}

实际主体Printer

为了体现生成Printer类需要时间,在初始化时通过heavyJob休眠5秒钟,由于实现了接口IPrintable,则重写了设置打印机名字和获取打印机名字的方法。当然最重要的就是print方法,因为代理类调用print方法时,需要初始化Printer类,进而调用该类print方法打印内容。

public class Printer  implements IPrintable {
    private String name;

    public Printer() {
        heavyJob("正在生成Printer实例...");
    }

    public Printer(String name) {
        this.name = name;
        heavyJob("正在生成Printer实例("+name+")...");
    }

    @Override
    public void setPrinterName(String name) {
        this.name=name;
    }

    @Override
    public String getPrinterName() {
        return name;
    }

    @Override
    public void print(String string) {
        System.out.println("======== "+name+" ========");
        System.out.println(string);
    }

    private void heavyJob(String msg){
        System.out.print(msg);
        for (int i = 0; i <5 ; i++) {
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("生成结束");
    }
}

代理人PrinterProxy

在该设计模式代理人不可或缺,它有两个变量一个是打印机名字,一个是打印机真实的对象real。对于设置打印机名字,会先判断真实的打印机对象real是否为空,如果不为空,表示已经存在真实对象则给真实的打印机对象设置名字,否则只会设置代理的名字。而print方法打印内容,也就是在该方法中我们才初始化真实打印机real,并调用最终的打印内容的方法print.当然已经初始化了就不会再次进行初始化。需要注意的是Printer并不知道PrinterProxy的存在,但是它们是紧密的联系在一起的。

public class PrinterProxy implements IPrintable{
    private String name;
    private Printer real;
    public PrinterProxy() {
    }

    public PrinterProxy(String name) {
        this.name = name;
    }

    @Override
    public synchronized  void setPrinterName(String name) {
        if (real!=null){
          real.setPrinterName(name);
        }
        this.name=name;
    }

    @Override
    public  String getPrinterName() {
        return name;
    }

    @Override
    public void print(String string) {
        realize();
        real.print(string);
    }

    private synchronized  void realize() {
        if (real==null){
            real=new Printer(name);
        }
    }
}

在代理类中我们给设置打印机名字和初始化真实打印机对象方法加了同步关键字synchronized,如果不使用当多个线程分别调用setPrintName和realize方法时可能会导致PrinterProxy类的名字和Printer类的名字不同的情况。

测试类

public class Main {
    public static void main(String [] args){
        IPrintable iPrintable=new PrinterProxy("Alice");
        System.out.println("现在的名字是"+iPrintable.getPrinterName());
        iPrintable.setPrinterName("Bob");
        System.out.println("现在的名字是"+iPrintable.getPrinterName());
        iPrintable.print("Hello ,world");
    }
}

由于PrinterProxy和Printer类都实现了IPrintable,因此测试类使用时不用在意究竟是调用多了代理PrinterProxy还是Printer。在复杂的业务中,我们可以在代理中指定哪些问题是它可以解决的,当遇到它不能解决的问题时,才去转交给本人去解决。

动态代理实现

在上面的介绍中,我们使用的是静态代理的方式来介绍代理模式,还有一种是动态代理,代理类是不存在的,而是动态生成的,那么我们该如何将上面示例的静态代理用动态代理实现呢,其实也不难,我们只需要了解Proxy类和InvocationHandler接口就可以实现。
它的实现可分为三步 ①,创建被代理类②,实现InvocationHandler接口,这是负责连接代理类和委托类的中间类必须实现的接口③,通过Proxy类新建代理类对象。在上述示例代码我们已经创建了被代理对象Printer,接下来创建一个实现InvocationHandler接口的类,为了在客户端更方便使用代理,我们直接在这个实现类中写一个创建代理的方法。代码如下

public class DynamicHandler implements InvocationHandler {
    // 目标对象
    private Object targetObject;

    //创建代理对象 这段也可以不在此类,也可以放在客户端里面
    public Object createProxy(Object targetOjbect) {
        this.targetObject = targetOjbect;
        /**
         * 创建代理对象
         * Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * loader:代理类的类加载器
         * interfaces:指定代理类所实现的接口
         * h:动态代理对象在调用方法的时候,关联的InvocationHandler对象
         */
        return Proxy.newProxyInstance(targetOjbect.getClass().getClassLoader(),
                targetOjbect.getClass().getInterfaces(), this);
    }

    /**
     * InvocationHandler接口所定义的唯一的一个方法,该方法负责集中处理动态代理类上的所有方法的调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上执行
     * @param proxy  代理类的实例
     * @param method  代理类被调用的方法
     * @param args   调用方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //触发真实对象之前或者之后可以做一些额外操作
        Object result = null;
        System.out.println("method:"+method.getName()+"proxy:"+proxy.getClass().getName());
        System.out.println("Before invoke ...");
        result = method.invoke(this.targetObject, args);//通过反射执行某个类的某方法
        System.out.println("After invoke ...");
        return result;
    }
}

然后我们将测试类做下更改,使用DynamicHandler创建代理类对象

public class Main {
    public static void main(String [] args){
        IPrintable iPrintable;
        DynamicHandler dynamicHandler=new DynamicHandler();
        iPrintable = (IPrintable) dynamicHandler.createProxy(new Printer("Alice"));
        System.out.println("现在的名字是"+iPrintable.getPrinterName());
        iPrintable.setPrinterName("Bob");
        System.out.println("现在的名字是"+iPrintable.getPrinterName());
        iPrintable.print("Hello ,world");
    }
}

通过上面的介绍你或许应该发现动态代理比静态代理使用方便多了。如果是静态代理,我们想要在接口中增加方法,需要修改我们很多代码,但是动态代理不需我们手动的去编写代理类,而是通过反射机制动态生成的,而使用动态代理就简单了许多,这样不仅简化了我们的工作也提高了系统的扩展性。好了,关于代理就介绍这么多,水平有限若有问题请指出,Hava a wonderful day.

如需文章中所写代码,请移步GitHub查看

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值