周其仁:停止改革,我们将面临三大麻烦

抛开立场观点不谈,且看周小平写一句话能犯多少语病

罗马尼亚的声明:小事件隐藏着大趋势——黑暗中的风:坚持做对的事相信未来的结果

布林肯突访乌克兰,为何选择去吃麦当劳?

中国不再是美国第一大进口国,贸易战殃及纺织业? 美国进一步延长352项中国商品的关税豁免期

生成图片,分享到微信朋友圈

自由微信安卓APP发布,立即下载! | 提交文章网址
查看原文

面包店里的编程狂想

陈树义 树哥聊编程 2022-10-25

↑↑ 点击上方关注「陈树义」↑↑



这是陈树义的第 041 期分享


作者 l 陈树义

来源 l 陈树义(ID:Spark-tree)

转载请联系授权(ID:Spark-tree)


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

//做蛋糕的机器
public interface CakeMachine{
    void makeCake();
}

//专门做水果蛋糕的机器
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("Making a fruit cake...");
    }
}

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

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

最简单的办法是直接修改水果蛋糕机的程序,做一台能做杏仁水果蛋糕的蛋糕机。这种方式对应的代码修改也很简单,直接在原来的代码上进行修改,生成一台专门做杏仁水果蛋糕的机器就好了,修改后的 FruitCakeMachien 类应该是这样子:

//专门做水果蛋糕的机器,并且加上一层杏仁
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("making a Fruit Cake...");
        System.out.println("adding apricot...");
    }
}

虽然上面这种方式实现了我们的业务需求。但是仔细想一想,在现实生活中如果我们遇到这样的一个需求,我们不可能因为一个顾客的特殊需求就去修改一台蛋糕机的硬件程序,这样成本太高!而且从代码实现角度上来说,这种方式从代码上不是很优雅,修改了原来的代码。根据「对修改封闭、对扩展开放」的思想,我们在尝试满足新的业务需求的时候应该尽量少修改原来的代码,而是在原来的代码上进行拓展。

那我们究竟应该怎么做更加合适一些呢?我们肯定是直接用水果蛋糕机做一个蛋糕,然后再人工撒上一层杏仁啦。我们需要做的,其实就是设计一个杏仁代理类(ApricotCakeProxy),这个代理类就完成撒杏仁这个动作,之后让蛋糕店直接调用即可代理类去实现即可。

//杏仁蛋糕代理
public class ApricotCakeProxy implements CakeMachine{
    private CakeMachine cakeMachine;
    public ApricotCakeProxy(CakeMachine cakeMachine) {
        this.cakeMachine = cakeMachine;
    }
    public void makeCake() {
        cakeMachine.makeCake();
        System.out.println("adding apricot...");
    }
}

这其实就对应了即使模式中的代理模式,虽然调用的是 ApricotCakeProxy 类的方法,但实际上真正做蛋糕的是 FruitCakeMachine 类。ApricotCakeProxy 类只是在 FruitCakeMachine 做出蛋糕后,撒上一层杏仁而已。而且通过代理,我们不仅可以给水果蛋糕撒上一层杏仁,还可以给巧克力蛋糕、五仁蛋糕等撒上一层杏仁。只要它是蛋糕(实现了 CakeMachine 接口),那么我们就可以给这个蛋糕撒上杏仁。

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

业务场景的复杂度往往千变万化,如果这个特别喜欢杏仁的客人,他也想在面包上撒一层杏仁,那我们怎么办?我们能够使用之前写的 ApricotCakeProxy 代理类么?不行,因为 ApricotCakeProxy 里规定了只能为蛋糕(实现了 CakeMachine 接口)的实体做代理。这种情况下,我们只能再写一个可以为所有面包加杏仁的代理类:ApricotBreadProxy。

//杏仁面包代理
public class ApricotBreadProxy implements BreadMachine{

    private BreadMachine breadMachine;

    public ApricotBreadProxy(BreadMachine breadMachine) {
        this.breadMachine = breadMachine;
    }

    public void makeBread() {
        breadMachine.makeBread();
        System.out.println("adding apricot...");
    }
}

我们可以看到我们也成功地做出了客人想要的杏仁红豆面包、杏仁葡萄干面包。对于客人来说,他肯定希望我们所有的产品都有一层杏仁,这样客人最喜欢了。为了满足客人的需求,那如果我们的产品有 100 种(饼干、酸奶等),我们是不是得写 100 个代理类呢?

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

接下来我们针对这个业务场景做一个代码的抽象实现。首先我们分析一下可以知道这种场景的共同点是希望在所有产品上都做「撒一层杏仁」的动作,所以我们就做一个杏仁动态代理(ApricotHandler)。

//杏仁动态代理
public class ApricotHandler implements InvocationHandler{

    private Object object;

    public ApricotHandler(Object object) {
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);    //调用真正的蛋糕机做蛋糕
        System.out.println("adding apricot...");
        return result;
    }
}

撒杏仁的代理写完之后,我们直接让蛋糕店开工:

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程序员的技术成长,用最简单的语言,让复杂的技术不再难懂。


↓↓点亮夜空中那颗最亮的❤️吧↓↓

文章有问题?点此查看未经处理的缓存