python:面向对象编程的设计原则之依赖倒置原则

python:面向对象编程的设计原则之依赖倒置原则,第1张

一,抽象与封装

coding 就是一个抽象的过程:在 OOP 中,我们将对象抽象为类。


  • 对象的状态与行为被抽象为类中的属性与方法。


  • 抽象的意义,就是为对象之的交互提供高效的接口。


封装就是在 coding 中隐藏数据与逻辑的过程:

  • 对象的状态和行为如何组织到类中,模块中的类该如何组织,更高层的代码该如何组织。


  • 抽象是多态和封装的基础,封装是接口的具体实现过程。


接口不一定需要由类来实现,只不过封装到类中有利于使用继承、多态、组合这些 OOP 基本特性以实现更高的代码可用性。


二,依赖倒置原则 (一)依赖正置

我们不断在强调 OOP 的软件设计应该考虑到后期可能出现的功能扩展需求,当我们的软件足够复杂之后,代码就会出现高低层次之分。


这个高低层次划分没有明确的界限,但可以肯定的是,绝对存在高层次代码对低层次代码的依赖。


低层次的代码可以是第三方 API,也可以是其他工作组定义的细节实现,高层次的代码通过继承、组合等方式依赖于它们提供的功能。


举个例子🌰:

class FXConverter:  # 创建低层转换器类
    def convert(self, from_currency, to_currency, amount):
        print(f'{amount} {from_currency} = {amount * 1.2} {to_currency}')
        return amount * 1.2


class App:          # 创建高层应用类
    def start(self):
        converter = FXConverter()
        converter.convert('EUR', 'USD', 100)


if __name__ == '__main__':
    app = App()
    app.start()

>>>
100 EUR = 120.0 USD

这使得高层代码严重依赖于低层代码,虽说不至于达到硬编码那种耦合程度,但在这种依赖“正置”的情况下,面对可能不够稳定的或者不可控的低层代码时,高低层的交互耦合度还是显得有点高。


  • 如果低层被破坏,则高层功能无法正确实现。


  • 如果高层需要使用其他 API,则需要修改高层本身代码。


(二)依赖倒置原则

我们需要一种方法来进一步降低耦合度,比如就像依赖倒置原则( Dependency Inversion Principle,DIP)要求的这样:

  • 高层不应该从低层导入任何东西,两者都应该依赖于抽象(例如,接口)。


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


    细节应该依赖于抽象。


在设计高层模块和低层模块之间的交互时,应该抽象它们之间的交互,让高层代码和底层代码都通过这层抽象来交互,避免了直接的交互动作,两者之间的耦合度自然也就降低了。


这个抽象可以通过抽象基类来实现:

from abc import ABC, abstractmethod

class CurrencyConverter(ABC):
    @abstractmethod
    def convert(self, from_currency, to_currency, amount) -> float:
        pass

而正是这层抽象,让原本从高到低的正向依赖,转为高层依赖抽象且低层也依赖抽象:

class OldConverter(CurrencyConverter):
    def convert(self, from_currency, to_currency, amount) -> float:
        print('Converting currency using OldConverter API, date: 2020-12-18')
        print(f'{amount} {from_currency} = {amount * 0.8156} {to_currency}')
        return amount * 0.8156


class NewConverter(CurrencyConverter):
    def convert(self, from_currency, to_currency, amount) -> float:
        print('Converting currency using NewConverter API, date: 2022-04-06')
        print(f'{amount} {from_currency} = {amount * 0.9158} {to_currency}')
        return amount * 0.9158


class App:
    def __init__(self, converter: CurrencyConverter):   # 注意:这里的converter是一个接口,不是一个细节实现
        self.converter = converter

    def start(self):
        self.converter.convert('USD', 'EUR', 100)

从而实现更加健壮的代码:

if __name__ == '__main__':
    converter_one = OldConverter()
    converter_two = NewConverter()

    app = App(converter_one)
    app.start()
    app = App(converter_two)
    app.start()

>>>
Converting currency using OldConverter API, date: 2020-12-18
100 USD = 81.56 EUR
Converting currency using NewConverter API, date: 2022-04-06
100 USD = 91.58 EUR

尽管 DIP 非常有用,但代码经过LSP、OCP、ISP 和 SRP 考虑后,已经拥有了相当的可重用性与健壮性了,这使得在实际中遵守 DIP 的代码就不太多。


Dependency Inversion in Python
OOP - Best way to populate Object

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/570115.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存