Python 中的依赖倒置

Dependency Inversion in Python

我已经开始将 SOLID principles 应用到我的项目中。所有这些对我来说都很清楚,除了依赖倒置,因为在 Python 中我们没有改变在另一个 class 中定义一些 class 类型的变量(或者也许我不知道)。所以我以两种形式实现了依赖倒置原则,想知道它们中的哪一个是正确的,我该如何纠正它们。这是我的代码:

d1.py:

class IFood:
    def bake(self, isTendir: bool): pass
    
class Production:
    def __init__(self):
        self.food = IFood()
    
    def produce(self):
        self.food.bake(True)
        
class Bread(IFood):
    def bake(self, isTendir:bool):
        print("Bread was baked")

d2.py:

from abc import ABC, abstractmethod
class Food(ABC):
    @abstractmethod
    def bake(self, isTendir): pass
    
class Production():
    def __init__(self):
        self.bread = Bread()
    
    def produce(self):
        self.bread.bake(True)
        
class Bread(Food):
    def bake(self, isTendir:bool):
        print("Bread was baked")
# define a common interface any food should have and implement
class IFood:
    def bake(self): pass
    def eat(self): pass

class Bread(IFood):
    def bake(self):
        print("Bread was baked")
    def eat(self):
        print("Bread was eaten")

class Pastry(IFood):
    def bake(self):
        print("Pastry was baked")
    def eat(self):
        print("Pastry was eaten")

class Production:
    def __init__(self, food): # food now is any concrete implementation of IFood
        self.food = food # this is also dependnecy injection, as it is a parameter not hardcoded

    def produce(self):
        self.food.bake()  # uses only the common interface

    def consume(self):
        self.food.eat()  # uses only the common interface

使用它:

ProduceBread = Production(Bread())
ProducePastry = Production(Pastry())

原理

Robert C. Martin 对依赖倒置原则的定义 由两部分组成:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

澄清一下...模块 可以是函数、class、文件...一段代码。

错误

假设您有一个程序需要您烤面包。

在更高的层次上,有一点你可以调用 cook()

一个不好的实现方式是创建一个既能做饭又能做面包的函数。

def cook():
    bread = Bread()
    bread.bake()

cook()

这样不好...

如您所见,cook 函数 依赖于 Bread.

那么如果你想烤饼干怎么办?

一个菜鸟的错误就是添加这样一个字符串参数:

def cook(food: str):
    if food == "bread":
        bread = Bread()
        bread.bake()
    if food == "cookies":
        cookies = Cookies()
        cookies.bake()

cook("cookies")

显然是错误的。因为通过添加更多食物,您会更改代码,并且您的代码会因许多 if 语句而变得一团糟。它几乎打破了所有原则

解决方案

所以你需要 cook 功能,它是一个更高级别的模块,而不是依赖于较低级别的模块,如 BreadCookies

所以我们唯一需要的就是可以烘焙的东西。我们会烤它。现在正确的方法是 实现接口 。在 Python 中没有必要,但强烈建议保持代码清洁和面向未来!

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

他们说。

现在让我们反转依赖关系!

from abc import ABC, abstractmethod
class Bakable(ABC):
    @abstractmethod
    def bake(self):
        pass

def cook(bakable:Bakable):
    bakable.bake()

现在 cook 函数 依赖于 抽象 。不在面包上,也不在饼干上,而是在抽象上。 Any any any Bakable 现在可以烘焙了。

通过实现接口,我们确信每个Bakable都会有一个bake()方法来做某事。

但是现在cook函数不需要知道了。 cook 函数将烘烤任何 Bakable.

依赖关系现在转到客户端。客户是想要烤东西的人。客户端是一段将要使用 cook 函数的代码。客户知道要烤什么。

现在通过查看 cook 函数,客户端知道 cook 函数等待接收一个 Bakable 并且只有一个 Bakable.

那我们来做点面包吧。

class Bread(Bakable):
    def bake():
        print('Smells like bread')

现在让我们创建一些 cookie!

class Cookies(Bakable):
    def bake():
        print('Cookie smell all over the place')

好的!现在我们来煮它们。

cookies = Cookies()
bread = Bread()
cook(cookies)
cook(bread)