设计原则-依赖倒置原则

依赖倒置原则


今天 我们来说一下,软件设计中另外一个原则,依赖倒置原则( DIP)

DIP 核心内容

Dependence Inversion Principle 缩写 DIP

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

这个原则的核心内容如下:

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象;

  • 抽象不应该依赖细节,细节应该依赖抽象。

看到这个内容的描述 可能会比较疑惑?

高层模块 就是要调用 底层模块啊, 业务类模块(高层模块) 就需要使用工具类的模块啊。 所以为啥 高层模块不能依赖底层模块呢?

请思考一下 如果高层模块 如果依赖 底层模块 意味着什么?

高层模块 我们认为 包含了一些 策略选择或者业务模型。正是由于这些高层的模块才使得是所在的应用程序区别与其他。 然后如果高层模块依赖 低层模块,如果底层模块一但发生了改动,对于高层模块影响是非常大的。

所以对于这种情况 我们应该尽量 使用 高层模块 独立于 底层模块,两者独立开来。

倒置接口的所有权

"don’t call us , we’ll call you " 不要调用我们,我们会调用你。

即 底层模块 实了高层 模块中声明并被高层模块中调用的接口。

依赖抽象

DIP 原则 一个简单的解释 “依赖于抽象” 就是不应该依赖于 具体的类,也就是说 程序中所有的依赖关系都应该是抽象类 或者接口 。

DIP 还可以简单理解成要依赖于抽象,由此,还可以推导出一些指导编码的规则:

  • 任何变量都不应该指向一个具体类;
  • 任何类都不应继承自具体类;
  • 任何方法都不应该覆写父类中已经实现的方法。

这里 当然 有的时候我们的程序并不是那么的严格,有违法的情况。 有时候就要创建一个具体的类,而创建实例的模块就会 依赖它们。 如果具体的类,是非常稳定的,几乎不太可能发生变化,也没有多大的问题。

在Java 中 String 类,Python 内置的类 list , dict, tuple,str

这些本身在Python中就不能修改. 因为这些类是稳定的,直接使用也没有啥问题。

举一个例子

ButtonLamp 按钮 可以控制 灯的开关, 而灯 有自己的打开,关闭方法。

如果在 Button 直接使用 lamp 的实现类

如下面的代码

class Lamp:
    """
    电灯
    可以认为是 底层模块

    """

    def turn_on(self):
        print("灯泡亮了")

    def turn_off(self):
        print("灯泡灭了")


class Button:
    """
    button 可以认为是 高层模块
    """

    def __init__(self, lamp: Lamp):
        self.lamp = lamp

    def poll(self):
        if 'some condition':
            self.lamp.turn_on()
        else:
            self.lamp.turn_off()

这里的代码 Button 依赖于 Lamp . 这样的 依赖关系 就会有问题, 当 Lamp 发生改动的时候 ,直接会影响到 Button 类, 并且此时 Button 只能控制 Lamp 对象。

解决方案

我们要想办法 找出一个抽象层 ,使 Button 不太依赖 Lamp .

计算机科学中的所有问题都可以通过引入一个间接层得到解决。
All problems in computer science can be solved by another level of indirection
—— David Wheeler

此时我们需要找到一个中间层, 来解决这个问题。

如下图所示:

请添加图片描述

来构建抽象层,首先思考 对应Lamp 这层的进行抽象,它就是需要 打开 和关闭 这两个方法。

class IDevices(metaclass=ABCMeta):

    @abstractmethod
    def turn_on(self):
        pass

    @abstractmethod
    def turn_off(self):
        pass

然后Lamp来实现这个接口

class Lamp(IDevices):
    """
    台灯
    """

    def turn_on(self):
        print("台灯亮了")

    def turn_off(self):
        print("台灯灭了")

我甚至还可以有其他类型的灯

class Light(IDevices):
    """
    电灯
    """

    def turn_on(self):
        print("灯泡亮了")

    def turn_off(self):
        print("灯泡灭了")

对于开关 也实现抽象层的方法。然后 把 抽象层对象传入,当成自己的依赖,注意此时 Switch 依赖的是 一个抽象的 device 不是一个具体的实现类,即此时Switch依赖于一个抽象,而不是细节。

class Switch(IDevices):
    """
    开关
    """

    def __init__(self, device: IDevices):
        self.device = device

    def turn_on(self):
        print("打开开关...")
        self.device.turn_on()

    def turn_off(self):
        print("关闭开关...")
        self.device.turn_off()
       
      
# 测试程序      
def main():
    # switch = Switch(Light())
    switch = Switch(Lamp())
    switch.turn_on()
    switch.turn_off()

这个例子 看起来比较简单,此时,理解 我们高层模块依赖底层模块的时候,需要依赖一个抽象,而不要依赖一个具体的实现。 这样就你可以 有效的 进行隔离开,高层 和 底层模块。 高层不在依赖底层模块。 底层模块无论如何改动都不会影响,高层模块。 高层模块依赖的是抽象,底层模块 则是具体不同的细节实现。

总结

DIP 帮助我们解决的问题 是 我们应该尽可能依赖抽象,这样的好处是 如果被依赖的对象发生变化的时候,我们几乎不太需要做出什么代码的改变。 如果我们依赖具体的对象,实例。那么一旦 底层模块发生了变化,我们可能就会改变很多高层的代码。软件设计原则基本上也写的差不多了,DIP 原则 可能更多的时候 比较适用于 写框架的代码,一般 业务层 也会用到 但不会特别多。从正常情况下,底层模块 应该是 相对稳定的代码,或者说几乎不太会改动的代码,如果底层的代码需要经常变动,就需要考虑 是否应该放在底层模块, 把经常改动的代码,应该尽可能 往 高层模块里面写,而不要写在底层模块里面。

参考文档

24 | 依赖倒置原则:高层代码和底层代码,到底谁该依赖谁?

设计模式六大原则—依赖倒置原则

小话设计模式原则之:依赖倒置原则DIP

敏捷软件开发:原则,模式,实践

分享快乐,留住感动. '2022-02-20 17:06:30' --frank
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值