设计原则之【依赖反转原则】

设计原则是指导我们代码设计的一些经验总结,也就是“心法”;面向对象就是我们的“武器”;设计模式就是“招式”。

以心法为基础,以武器运用招式应对复杂的编程问题。

来吧,通过生活中一个小场景,一起系统学习这6大设计原则。

SOLID原则–SRP单一职责原则

SOLID原则–OCP开放封闭原则

SOLID法则–LSP里式替换原则

SOLID原则–ISP接口隔离原则

SOLID原则–DIP依赖反转原则

LOD迪米特法则

表妹让我帮她修收音机,把我给整懵了…

表妹:哥啊,我的电脑蓝屏死机了😓

我:我帮你看看。

一顿操作…

我:你这里有两个内存条,但是其中一根坏了,我现在把它给卸了,暂时用那根好的。

表妹:哇!果然可以了,真​棒👍对了,哥啊,我老爸有台82年的收音机,坏了好一阵子了,你也帮他修修呗?

我:哈…?这,我就不会啦。

表妹:为啥?电脑都会,小收音机怎么可能难倒你呢?

我:哈哈,因为,它违背了依赖反转原则。

表妹:哈?啥意思?


高层模块不要依赖底层模块。高层模块和底层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

如下图所示:

在这里插入图片描述

为什么收音机很难修呢?

因为各个部件相互依赖,如左图所示,任何问题都可能涉及其他部件,不懂的人根本没法修。但是电脑不同,因为无论主板、CPU、内存、硬盘都是针对接口设计的,如右图所示,那么,不管哪一个出问题,都可以在不影响别的部件的前提下,进行修改或替换。

比如,现在我们需要实现一个披萨店,该店售有芝士披萨、海鲜披萨、超级至尊等品种。可能我们很容易想到,披萨店是上层模块,具体的披萨品种是下层模块,如果我们把披萨店和披萨品种的关系画成一张图,应该是这样的:

在这里插入图片描述

接下来,我们来看一下代码实现:

public class PizzaStore {

    public void cheesePizza() {
        System.out.println("芝士披萨");
    }

    public void seafoodPizza() {
        System.out.println("海鲜披萨");
    }
    
    public void superSupreme() {
        System.out.println("超级至尊");
    }
}

我们来模拟上层调用:

public static void main(String[] args) {
    PizzaStore pizzaStore = new PizzaStore();
    pizzaStore.cheesePizza();
    pizzaStore.superSupreme();
    pizzaStore.seafoodPizza();
}

如果该店业务的扩展,新增很多品种,那么,这个时候,就需要从底层实现到高层调用依次地修改代码。

我们需要在PizzaStore类中新增披萨的品种,也需要在高层调用中增加调用,这样一来,系统发布后,其实是非常不稳定的。虽然这个例子比较简单,修改也不会引入新的bug,但是,实际项目会复杂很多。

最理想的情况是,我们已经编写好的代码可以“万年不变”,这就意味着已经覆盖的单元测试可以不用修改,已经存在的行为可以保证保持不变,这就意味着稳定。任何代码上的修改带来的影响都是有未知风险的,不论看上去多么简单。

如何降低高低层代码之间的耦合?

比如,商品经济的萌芽时期,出现了物物交换。如果你要买一台手机,卖手机的人让你拿一头猪来换,但是你手里没有猪,这时,你就要去找一个卖猪的老板,但是他要你拿羊来跟他换,你也没有羊,继续去找卖羊的人…

你看,这一连串的对象依赖,造成了严重的耦合灾难。解决这一问题的最好办法就是,买卖双方都依赖一个抽象–货币,通过货币来进行交换,这样一来耦合度就大为降低了。

我们现在的代码是上层直接依赖底层实现,现在我们需要定义一个抽象的IPizza接口,来对这种强依赖进行解耦。如下图所示:

在这里插入图片描述

我们再来看一下代码:

先定义一个Pizza的抽象接口IPizza:

public interface IPizza {
    void pizza();
}

接下来就是不同的品种的实现类:

public class cheesePizza implements IPizza {
    @Override
    public void pizza() {
        System.out.println("芝士披萨");
    }
}

public class seafoodPizza implements IPizza {
    @Override
    public void pizza() {
        System.out.println("海鲜披萨");
    }
}

public class superSupreme implements IPizza {
    @Override
    public void pizza() {
        System.out.println("超级至尊");
    }
}

这时上层PizzaStore类依赖IPizza接口即可:

public class PizzaStore {
    public void sellPizza(IPizza p) {
        p.pizza();
    }
}

接下来就是上层调用:

public static void main(String[] args) {
    PizzaStore pizzaStore = new PizzaStore();
    pizzaStore.sellPizza(new cheesePizza());
    pizzaStore.sellPizza(new seafoodPizza());
    pizzaStore.sellPizza(new superSupreme());
}

你看,在这种设计下,无论该披萨店的品种怎么扩增,只需新建一个该品种的实现类即可,而不需要修改底层的代码。后面,如果该披萨店的业务继续扩大,除了卖披萨,还卖其他小吃,饮料酒水等,同样,只需分别抽象出小吃和饮料酒水两个接口,让上层调用只依赖这些接口即可。

总结

以抽象为基准比以细节为基准搭建起来的架构要稳定得多。

因此,在拿到需求后,要面向接口编程,先顶层设计再细节地设计代码结构。

好啦,每一种设计原则是否运用得当,应该根据具体业务场景,具体分析。

参考

极客时间专栏《设计模式之美》

《大话设计模式》

https://www.jianshu.com/p/c3ce6762257c

https://www.wmyskxz.com/2019/11/18/tan-yi-tan-yi-lai-dao-zhi-yuan-ze/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值