【Java】小例子巧妙理解代理模式

代理模式概述

给实际对象生成一个代理对象,将代理对象交给用户,避免用户操作实际对象。

可以做到不修改实际对象的代码,而为实际对象的方法添加新功能。比如在方法前后打印时间可以知道该方法的执行耗时。

静态代理

在编译期间就已经确定了代理对象和实际对象之间的关系。代理对象是在编译期间就生成的。

例子

小明想买个美版的mac,但是他没渠道,所以只能找一个代购去帮忙购买。

解析

  1. 要执行的动作是购买mac

  2. 实际对象是小明

  3. 代理对象是代购

代码实现

首先,声明一个动作购买mac

public interface Action {
    void buyMac();
}

其次,创建一个实际对象小明,并让其实现上面的购买mac动作。

public class XiaoMing implements Action {

    @Override
    public void buyMac() {
        System.out.println("小明购买mac");
    }
}

然后,创建一个代理对象代购,也让其实现上面的购买mac动作。

public class MyProxy implements Action {
    private Action target;

    public MyProxy(Action target) {
        // 构造函数需要告诉代购,是谁需要代购
        this.target = target;
    }

    @Override
    public void buyMac() {
        target.buyMac();
    }
}

最后,实际使用。

MyProxy myProxy = new MyProxy(new XiaoMing());
myProxy.buyMac();

使用代理模式的原因

上面的代码看上去似乎有点多余,明明可以只创建一个小明,然后里面在声明一个购买mac的方法就可以,为什么还要多此一举的声明一个接口,创建一个代理对象呢?

假如现在新增一个需求:需要记录购买了多少台mac,如果超过了10台,那么就不继续代购了,因为容易被查水表/狗头。

那该怎么实现呢?假如没有使用代理的方式,那么代码可能是这样的:

首先,创建一个Accountant类,用于管理数量。

public class Accountant {
    // 记录购买的mac数量
    private static int macCount = 0;

    /**
     * 购买mac数量+1
     */
    public static void addNewMac(){
        macCount++;
    }

    /**
     * 获取当前购买了多少台mac
     * @return int 
     */
    public static int getMacCount(){
        return macCount;
    }
}

然后,在每次购买mac的时候,都调用Account类的方法进行判断。

public class XiaoMing implements Action {

    @Override
    public void buyMac() {
        if (Accountant.getMacCount() >= 10){
            System.out.println("已购买数量超标");
        }else {
            System.out.println("小明购买mac");
            Accountant.addNewMac();// 已购mac数量+1
        }
    }
}

这样看上去很美好不是吗?

假如此时又有一个小红需要购买mac,会再创建一个XiaoHong

public class XiaoHong implements Action {
    @Override
    public void buyMac() {
        if (Accountant.getMacCount() >= 10){
            System.out.println("已购买数量超标");
        }else {
            System.out.println("小红购买mac");
            Accountant.addNewMac();// 已购mac数量+1
        }
    }
}

假如此时小黑、小白、小兰也想购买mac呢?怎么办?继续创建XiaoHeiXiaoBaiXiaoLan吗?

这样,每次在购买mac之前都需要写一次判断方法,太傻了。程序员最擅长什么?

把重复的动作变成自动的。

这个时候,使用代理模式来处理的话,只需要在代理对象中写一次判断就可以了。

public class MyProxy implements Action {
    private Action target;

    public MyProxy(Action target) {
        // 构造函数需要告诉代购,是谁需要代购
        this.target = target;
    }

    @Override
    public void buyMac() {
        if (Accountant.getMacCount() >= 10){
            System.out.println("已购买数量超标");
        }else {
            target.buyMac();
            Accountant.addNewMac();// 已购mac数量+1
        }
    }
}

看啊,人类的智慧!理论上少写了无数行重复代码!太厉害了!奥利给!

动态代理

在运行时,才确立代理对象和实际对象间的关系。代理对象是在运行时生成的。

例子

现在这个代购生意做大了,不但能代购mac了,还能代购手机、奶粉、球鞋等你能想到的一切东西。而且为了炫耀,他每次接到单都要发朋友圈。

解析

  1. 要执行的动作是代购手机、奶粉、球鞋等

  2. 实际对象会存在很多个

  3. 每次执行动作前都要发朋友圈

代码实现

创建若干个接口,分别有购买手机、购买奶粉、购买球鞋等。(这里为了减少代码量,只写了一个购买手机的例子)

public interface Action1 {
    void bugPhone();
}

同样,创建实际对象(要购买手机的人),并实现上面的接口。

public class XiaoHei implements Action1 {
    @Override
    public void bugPhone() {
        System.out.println("小黑购买phone");
    }
}

现在就存在问题了,如果还像使用静态代理一样的方式去创建一个代理对象的话,那么类文件数量肯定会非常夸张。那有没有什么办法可以不创建类文件,但是又能生成类呢?人类的智慧是无穷的,动态代理就出来了!

// 1.实例化实际对象
final XiaoHei xiaoHei = new XiaoHei();

// 2.获取类加载器
ClassLoader loader = xiaoHei.getClass().getClassLoader();

// 3.获取所有接口class,这里的XiaoHei只实现了Action1.class
Class[] interfaces = xiaoHei.getClass().getInterfaces();

// 4.实例化出代理对象
Action1 proxy =(Action1) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        // 代理对象每次执行方法时,都会调用到这里
        broadcast();// 发朋友圈
        return method.invoke(xiaoHei,objects);// 执行实际对象的方法
    }
});

// 5.执行代理对象的方法
proxy.bugPhone();

使用动态代理的原因

通过上面的例子,可以看到,不需要再创建代理类文件了,直接在代码中生成了代理类,显著的减少了文件数量。

发布了24 篇原创文章 · 获赞 7 · 访问量 8677

猜你喜欢

转载自blog.csdn.net/d745282469/article/details/104607941
今日推荐