【问题标题】:Why is replacing conditionals with polymorphism useful?为什么用多态性替换条件有用?
【发布时间】:2021-05-11 22:50:44
【问题描述】:

refactoring.guru 上显示的“用多态性替换条件”一直困扰着我,我想澄清一下。我在下面的 python 中重新创建了示例:

IF 语句代码

def get_speed(bird):
    base_speed = 10

    if bird == "AfricanBird":
        return base_speed * 2 - 10
    elif bird=="EuropeanBird":
        return base_speed * 1.85 - 12
    elif bird=="NorwegianBlue":
        return base_speed * 2.5 - 11
    else:
        raise ValueError("Bird type invalid")

if __name__=="__main__":
    b = "EuropeanBird"
    speed = get_speed(b)
    print(speed)

用多态替换 IF 语句

from abc import abstractmethod, ABC

class Bird(ABC):
    def get_base_speed(self):
        return 10

    @abstractmethod
    def get_speed(self):
        pass

class AfricanBird(Bird):
    def get_speed(self):
        return self.get_base_speed()*2-10

class EuropeanBird(Bird):
    def get_speed(self):
        return self.get_base_speed()*1.85-12

class NorwegianBlue(Bird):
    def get_speed(self):
        return self.get_base_speed()*2.5-11

if __name__=="__main__":
    # This *still* has to be specified by the user somewhere.
    # For example in a GUI or input text file.
    b = "EuropeanBird"

    # Here is the if statement again!!
    if b == "AfricanBird":
        b = AfricanBird()
    elif b=="EuropeanBird":
        b = EuropeanBird()
    elif b=="NorwegianBlue":
        b = NorwegianBlue()
    else:
        raise ValueError("Bird type invalid")

    print(b.get_speed())

我通过允许您从基类继承并重新定义行为而不是在if 语句中添加新分支来获得多态性增加通用性的部分。但是,归根结底,您仍然需要在主代码中的某处决定要使用基类的 which 实现(我的第二个示例中的第 26 行),这会强制使用 @987654325 @ 语句重新出现。

我错过了重点吗?有什么办法可以彻底消除客户端代码中的条件语句?

【问题讨论】:

  • 如果您投票结束,请解释原因。

标签: python oop design-patterns polymorphism conditional-statements


【解决方案1】:

在 IF 语句变体中,案例必须与计算一起处理。在 POLYMORPHISM 变体中,案例可以在接收到输入时就被处理,但也可以任意向前推进。使用多态,将案例决策和结果解耦;甚至可以重复使用相同的决策来得出多个结果。

简而言之,两者都需要在某处做出决定,但多态性允许在某处进行选择。


虽然从逻辑上讲,something 必须有条件地在案例之间切换,这可以在不显式枚举 if 分支的情况下完成。

最简单的方法是有一个从标识符到类型的表。基类可以封装这个:

class Bird(ABC):
    _birdies = {}
    _base_speed = 10

    @classmethod
    def breed(cls, kind: str):
        """Create a new bird of a given kind"""
        return cls._birdies[kind]()

    # register subclasses for lookup on definition
    def __init_subclass__(cls):
        Bird._birdies[cls.__name__] = cls

    @property
    @abstractmethod
    def speed(self):
        raise NotImplementedError

class AfricanBird(Bird):
    @property
    def speed(self):
        return self._base_speed * 2 - 10

class EuropeanBird(Bird):
    @property
    def speed(self):
        return self._base_speed * 1.85 - 12

Bird.breed("AfricanBird")  # <__main__.AfricanBird at 0x1106d1fa0>

请注意,该类仅用作隐藏表格的前端。除了假设所有子类都可以在没有参数的情况下互换构造之外,这里没有使用多态性。

【讨论】:

  • 那么,实际上,没有办法完全消除条件...?这就是我应该从中得到的吗?对于我的第二个示例,是否有任何设计模式可以减轻我在 __main__ 中所做的事情?
  • @user32882 如果你的代码有(逻辑)分支,something 必须实现它们。 if 的替代品是查找表(tps = {"AfricanBird": AfricanBird, ...},然后是 instance = tps[name]())和动态试验(技术上简单,但评论太长),但最终它们在功能上等同于“做出决定”,也就是条件。跨度>
  • @user32882 我添加了一个在基类中封装动态扩展查找表的示例。
【解决方案2】:

在您的示例中,您将单纯的字符串转换为完整的类层次结构。由于所有需要的只是处理一个字符串,并且输入是该字符串,因此多态性除了学习之外什么都不增加。

当各种对象在内部创建时,以及当这些对象具有某些具有特殊性的共同行为时,情况就会有所不同。事实上,选择应该在构思时出现,在编写任何代码行之前。您分析要处理的对象,以及它们必须实现的行为。此时,可能会出现类的层次结构。

事实上,只有在您跳过开发中的建模阶段,并且发现自己在整个代码中不断重复相同的条件时,才应该使用多态性替换一堆条件。几十年来我一直在使用 Python、Java 和 C++,并且从未用多态性代替条件:要么模型在构思时表现出层次结构,要么必须首先实现,要么没有底层对象和多态只会增加无用的复杂性。

TL/DR:如果您发现自己用多态性替换了条件,那只是意味着您在编写代码时没有首先考虑要开发什么以及应该如何开发。

【讨论】:

  • 从别人(科学家)开发的遗留代码开始,没有OOP的概念,好的设计是工业界的普遍现象。这没什么不好。如果您一直在使用这些语言进行编码而从未进行过这种替换,那么我会争辩说您不是回答这个问题的最佳人选?最后,refactoring.guru(实际上是 GoF 的书)并不认为用多态性替换条件语句会引入无用的复杂性,所以我不同意这一点。
  • 我不得不将 Fortran IV(没有 OOP 的概念)代码移植到 C++ 中。高级部分从头开始重写,以便能够使用 OO 建模更好地构建应用程序。只有低级(计算)例程被转换为最小值。恕我直言,使用 OOP 重构以意大利面条风格编写且几乎没有测试的代码可能比从头开始重写更难。但你在某一点上是对的,这不过是我的看法:-)
猜你喜欢
  • 2014-08-22
  • 1970-01-01
  • 1970-01-01
  • 2018-07-28
  • 2013-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多