Python中的依赖倒置

uqcuzwp8  于 2023-05-08  发布在  Python
关注(0)|答案(3)|浏览(198)

我已经开始将SOLID principles应用到我的项目中。除了依赖倒置,所有这些对我来说都很清楚,因为在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")

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")
agyaoht7

agyaoht71#

原则

Robert C. Martin对依赖倒置原则的定义包括两部分:
1.高级模块不应该依赖于低级模块。两者都应该依赖于抽象。
1.抽象不应该依赖于细节。细节应该依赖于抽象。
只是澄清一下。。一个模块可以是一个函数,一个类,一个文件...一段代码
错误
假设你有一个程序需要你烤面包。
在更高的级别上,可以调用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中,这不是必要的,但强烈建议保持代码整洁和面向未来!
如果它看起来像鸭子,游泳像鸭子,嘎嘎叫像鸭子,那么它可能是一只鸭子。

  • 他们说 *

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

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

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

现在cook函数依赖于抽象**。不是在面包上,不是在饼干上,而是在抽象上。任何任何任何Bakable现在都可以烘焙。
通过实现接口,我们确信每个Bakable都将有一个bake()方法来做一些事情。
但是现在cook函数不需要知道了。cook函数将烘焙任何Bakable

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

现在通过查看cook函数,客户端知道cook函数等待接收Bakable,并且只接收Bakable
让我们来制作一些面包。

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

现在让我们创建一些cookie!

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

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

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

iugsix8n2#

# 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 dependency 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())
n9vozmp4

n9vozmp43#

(非常)可以说,Python的一个伟大特性是动态/鸭子类型。只要传入的对象实现了函数所需的特定方法和成员,一切都应该正常工作。
通过使用静态类型检查器和类型库的Protocol特性,我们可以实现依赖反转,同时仍然享受动态类型的自由。值得注意的是,使用ABC@abstractmethod不会检查实现方法上的兼容签名,也不允许duck类型。
当使用Protocol时,实现的类甚至不需要引用或知道协议/接口,允许使用外部类。在有多个开发者的代码库中,提交钩子可以验证任何内部实现类的任何更改。

使用协议

from typing import Protocol

class Food(Protocol):
    def bake(self, isTendir: bool) -> str: ...
    

class Bread:
    def bake(self, isTendir: bool) -> int:
        return 4
    

class Duck:
    def bake(self, isTendir: bool) -> str:
        return "quack"
    

def bake(food: Food) -> str:
    return food.bake(True)

def main():
    bake(Bread())  # Static typing check error: "int" is incompatible with "str"
    bake(Duck())

使用ABC的进一步实验

静态类型检查下面的代码段(在Vscode中使用PyLance)很乐意允许行

bake(Bread())

它没有正确地实现bake方法(返回int而不是str)。
并且静态类型检查器在第行标记错误,

bake(Duck())

这是完全有效/“安全”的Python代码,将运行。有时候你可能想强制一个对象实现一个特定的抽象基类,但我认为这通常不是依赖倒置的必要条件。

from abc import ABC, abstractmethod

class Food(ABC):
    @abstractmethod
    def bake(self, isTendir: bool) -> str: ...
    

class Bread(Food):
    def bake(self, isTendir: bool) -> int:
        return 4
    

class Duck:
    def bake(self, isTendir: bool) -> str:
        return "quack"
    

def bake(food: Food) -> str:
    return food.bake(True)

def main():
    bake(Bread())
    bake(Duck())  # Static type checking error: "Duck" is incompatible with "Food"

相关问题