小黑最近手头有点紧,于是想着去面包店里兼职赚点外快。刚刚到面包店里上班就看到面包师傅在做蛋糕和面包,但是都是手工一个个在做,半个小时才能做出一个蛋糕。程序员出身的小黑就不由自主地思考起来,要是能开发出一台机器,它能自动做蛋糕,那该多好啊。10分钟就能做出一个蛋糕,10分钟就能做好一批面包,这样效率又高,又可以减掉人力成本。我们用 Java 语言描述一下这个场景:

面包店里的编程狂想
小黑把这个想法告诉了店长,店长说:不错,小伙子很有想法。我会尝试着去做一做,要是做出来了肯定能大大提高生产效率!不知道过了多久,小黑发现店里竟然真的用机器来做蛋糕和面包了。

但生活中的场景往往是复杂多变的。就在小黑惊讶的时候,店里来了一个顾客,他想要一个水果蛋糕,但他特别喜欢杏仁,希望在水果蛋糕上加上一层杏仁。这时候我们应该怎么做呢?

最简单的办法是直接修改水果蛋糕机的程序,做一台能做杏仁水果蛋糕的蛋糕机。这种方式对应的代码修改也很简单,直接在原来的代码上进行修改,生成一台专门做杏仁水果蛋糕的机器就好了,修改后的 FruitCakeMachien 类应该是这样子:
面包店里的编程狂想
虽然上面这种方式实现了我们的业务需求。但是仔细想一想,在现实生活中如果我们遇到这样的一个需求,我们不可能因为一个顾客的特殊需求就去修改一台蛋糕机的硬件程序,这样成本太高!而且从代码实现角度上来说,这种方式从代码上不是很优雅,修改了原来的代码。根据「对修改封闭、对扩展开放」的思想,我们在尝试满足新的业务需求的时候应该尽量少修改原来的代码,而是在原来的代码上进行拓展。

那我们究竟应该怎么做更加合适一些呢?我们肯定是直接用水果蛋糕机做一个蛋糕,然后再人工撒上一层杏仁啦。我们需要做的,其实就是设计一个杏仁代理类(ApricotCakeProxy),这个代理类就完成撒杏仁这个动作,之后让蛋糕店直接调用即可代理类去实现即可。
面包店里的编程狂想
这其实就对应了即使模式中的代理模式,虽然调用的是 ApricotCakeProxy 类的方法,但实际上真正做蛋糕的是 FruitCakeMachine 类。ApricotCakeProxy 类只是在 FruitCakeMachine 做出蛋糕后,撒上一层杏仁而已。而且通过代理,我们不仅可以给水果蛋糕撒上一层杏仁,还可以给巧克力蛋糕、五仁蛋糕等撒上一层杏仁。只要它是蛋糕(实现了 CakeMachine 接口),那么我们就可以给这个蛋糕撒上杏仁。

通过代理实现这样的业务场景,这样我们就不需要在原来的类上进行修改,从而使得代码更加优雅,拓展性更强。如果下次客人喜欢葡萄干水果蛋糕了了,那可以再写一个 CurrantCakeProxy 类来撒上一层葡萄干,原来的代码也不会被修改。上面说的这种业务场景就是代理模式的实际应用,准确地说这种是静态代理。

业务场景的复杂度往往千变万化,如果这个特别喜欢杏仁的客人,他也想在面包上撒一层杏仁,那我们怎么办?我们能够使用之前写的 ApricotCakeProxy 代理类么?不行,因为 ApricotCakeProxy 里规定了只能为蛋糕(实现了 CakeMachine 接口)的实体做代理。这种情况下,我们只能再写一个可以为所有面包加杏仁的代理类:ApricotBreadProxy。
面包店里的编程狂想
我们可以看到我们也成功地做出了客人想要的杏仁红豆面包、杏仁葡萄干面包。对于客人来说,他肯定希望我们所有的产品都有一层杏仁,这样客人最喜欢了。为了满足客人的需求,那如果我们的产品有 100 种(饼干、酸奶等),我们是不是得写 100 个代理类呢?

有没有一种方式可以让我们只写一次实现(撒杏仁的实现),但是任何类型的产品(蛋糕、面包、饼干、酸奶等)都可以使用呢?其实在 Java 中早已经有了针对这种情况而设计的一个接口,专门用来解决类似的问题,它就是动态代理 —— InvocationHandler。

接下来我们针对这个业务场景做一个代码的抽象实现。首先我们分析一下可以知道这种场景的共同点是希望在所有产品上都做「撒一层杏仁」的动作,所以我们就做一个杏仁动态代理(ApricotHandler)。
面包店里的编程狂想
撒杏仁的代理写完之后,我们直接让蛋糕店开工:

public class CakeShop {

public static void main(String[] args) {

//动态代理(可以同时给蛋糕、面包等加杏仁)

//给蛋糕加上杏仁

FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();

ApricotHandler apricotHandler = new ApricotHandler(fruitCakeMachine);

CakeMachine cakeMachine = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),

fruitCakeMachine.getClass().getInterfaces(),

apricotHandler);

cakeMachine.makeCake();

//给面包加上杏仁

RedBeanBreadMachine redBeanBreadMachine = new RedBeanBreadMachine();

apricotHandler = new ApricotHandler(redBeanBreadMachine);

BreadMachine breadMachine = (BreadMachine) Proxy.newProxyInstance(redBeanBreadMachine.getClass().getClassLoader(),

redBeanBreadMachine.getClass().getInterfaces(),

apricotHandler);

breadMachine.makeBread();

}

}

与静态代理相比,动态代理具有更加的普适性,能减少更多重复的代码。试想这个场景如果使用静态代理的话,我们需要对每一种类型的蛋糕机都写一个代理类(ApricotCakeProxy、ApricotBreadProxy、ApricotCookieProxy 等)。但是如果使用动态代理的话,我们只需要写一个通用的撒杏仁代理类(ApricotHandler)就可以直接完成所有操作了。直接省去了写 ApricotCakeProxy、ApricotBreadProxy、ApricotCookieProxy 的功夫,极大地提高了效率。

简单地说,静态代理只能针对特定一种产品(蛋糕、面包、饼干、酸奶)做某种代理动作(撒杏仁),而动态代理则可以对所有类型产品(蛋糕、面包、饼干、酸奶等)做某种代理动作(撒杏仁)。

小黑表示一切编程思想都可以从现实生活中找到合适的例子,Java 不愧是经典的面向对象语言。看到这里,你们理解静态代理和动态代理的区别了吗?欢迎留言告诉我。

欢迎工作一到五年的Java工程师朋友们加入Java架构开发:744677563

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导