Python 中的依赖倒置

Bob*_*lds 8 python oop dependency-injection python-3.x solid-principles

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

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")
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

CRI*_*SPR 76

原则

\n

Robert C. Martin\xe2\x80\x99 对依赖倒置原理的定义\n由两部分组成:

\n
\n
    \n
  1. 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
  2. \n
  3. 抽象不应该依赖于细节。细节应该取决于抽象。
  4. \n
\n
\n

只是为了澄清......一个模块可以是一个函数、一个类、一个文件...一段代码。

\n

这个错误

\n

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

\n

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

\n

实现这一点的一个糟糕方法是创建一个既做饭又创建面包的函数。

\n
def cook():\n    bread = Bread()\n    bread.bake()\n\ncook()\n
Run Code Online (Sandbox Code Playgroud)\n

不是很好...

\n

如您所见,该cook函数取决于Bread.

\n

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

\n

菜鸟错误是添加一个字符串参数,如下所示:

\n
def cook(food: str):\n    if food == "bread":\n        bread = Bread()\n        bread.bake()\n    if food == "cookies":\n        cookies = Cookies()\n        cookies.bake()\n\ncook("cookies")\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

解决方案

\n

因此,您需要的cook功能是较高级别的模块,而不是依赖于较低级别的模块,例如BreadCookies

\n

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

\n
\n

如果它看起来像鸭子,游泳像鸭子,嘎嘎叫像鸭子,那么它可能就是鸭子。

\n
\n

他们说。

\n

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

\n
from abc import ABC, abstractmethod\nclass Bakable(ABC):\n    @abstractmethod\n    def bake(self):\n        pass\n\ndef cook(bakable:Bakable):\n    bakable.bake()\n
Run Code Online (Sandbox Code Playgroud)\n

现在cook功能取决于抽象。不是在面包上,不是在饼干上,而是在抽象上。现在任何任何都Bakable可以烘烤。

\n

通过实现该接口,我们确信每个接口Bakable都会有一个bake()执行某些操作的方法。

\n

但现在cook函数不需要知道。\ncook 函数将烘焙任何Bakable.

\n

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

\n

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

\n

那么让我们来制作一些面包吧。

\n
class Bread(Bakable):\n    def bake(self):\n        print('Smells like bread')\n
Run Code Online (Sandbox Code Playgroud)\n

现在让我们创建一些cookie!

\n
class Cookies(Bakable):\n    def bake(self):\n        print('Cookie smell all over the place')\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
cookies = Cookies()\nbread = Bread()\ncook(cookies)\ncook(bread)\n
Run Code Online (Sandbox Code Playgroud)\n

  • 这是一个很好的解释! (8认同)

Nik*_* M. 12

# 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
Run Code Online (Sandbox Code Playgroud)

用它:

ProduceBread = Production(Bread())
ProducePastry = Production(Pastry())
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果将 IFood 接口与类型提示一起使用,则显式创建 IFood 接口会更有意义,如下所示:`def __init__(self, food: IFood)` (4认同)
  • 您可以考虑在“IFood”抽象方法中创建“bake”和“eat”,和/或如果您想要更多限制/自记录代码,则让它们引发“NotImplementedError”。 (3认同)
  • @timgeb,对,我已经在我的项目中做到了。我们是可怜的无界面家伙)) (2认同)