【问题标题】:Template Design Pattern in Python question on overriding the template methodPython中的模板设计模式关于覆盖模板方法的问题
【发布时间】:2020-03-05 04:35:14
【问题描述】:

我是否违反了 Python 中的模板设计模式?

所以我有一个基类,我在其中创建了模板方法,我想创建两个额外的类,我可以用它们来执行正常的函数返回和生成器函数 yield?

我必须重写 do_something 方法并将语句从“return”更改为关键字“yield”,或者我应该做其他替代设计代码更改吗?

from abc import ABC, abstractmethod

class BaseClass(ABC):
    def do_something(self):
        x = self.do_step1()
        y = self.do_step2()
        return x + y

    @abstractmethod
    def do_step1(self):
        pass

    @abstractmethod
    def do_step2(self):
        pass

class ReturnClass(BaseClass):
    def do_something(self):
        x = self.do_step1()
        y = self.do_step2()
        return x + y

    def do_step1(self):
        return 1

    def do_step2(self):
        return 1

class YieldClass(BaseClass):
    def do_something(self):
        x = self.do_step1()
        y = self.do_step2()
        yield x + y

    def do_step1(self):
        return 2

    def do_step2(self):
        return 2

class ConcreteReturnClass(ReturnClass):
    def do_step1(self):
        return 3

    def do_step2(self):
        return 3

class ConcreteYieldClass(YieldClass):
    def do_step1(self):
        return 4

    def do_step2(self):
        return 4

if __name__ == '__main__':
    return_class = ConcreteReturnClass();
    print(return_class.do_something())

    yield_class = ConcreteYieldClass();
    print(next(yield_class.do_something()))

我的目标是创建一个基类作为模板,但我想重用同一个类并将实现从只返回一个关键字更改为像生成器函数一样产生。我认为我违反了原则,但我找不到可行的替代方案。

【问题讨论】:

    标签: python python-3.x design-patterns


    【解决方案1】:

    BaseClass 类中,do_something 将是您的模板方法,也就是说,它“根据子类覆盖以提供具体行为的抽象操作定义算法”。在这种情况下,这些操作由抽象方法 do_step1do_step2 提供,需要被子类覆盖。

    然而,在子类ReturnClass 中,您已经覆盖了模板方法do_something 本身。虽然没有被禁止,但并不常见。但是在这种情况下,您提供的实现似乎与基类中的实现相同,因此这无济于事。我不知道你为什么说你“必须重写 do_something 方法。”

    x = ReturnBase(): print(x.do_something()) // prints 2
    

    上面打印2,不管do_something 是否被覆盖是ReturnClass假设你提供了一个相同的定义

    冒着听起来迂腐的风险:

    但我看到的真正问题是:一个类或接口代表一个契约。对于每一种方法,都有隐含的先决条件,在调用之前必须满足这些先决条件。对于实现stack 并具有pop 操作的类,调用pop 之前的前提条件是堆栈中必须至少有一个元素才能使操作成功。同样,每个方法调用都有一个隐含的后置条件。在最后一个示例中,承诺是pop 方法将在堆栈中留下比调用前少一个的项目。当子类重写基类方法时,它必须遵守隐含的约定,即对方法调用不要求更强的先决条件,也不承诺在方法返回时提供更少的内容。

    在您的示例中,您有两个子类,它们的 do_something 方法返回完全不同类型的结果。这从根本上违反了Liskov substitution principle,即如果 S 是 T 的子类型,则 T 类型的对象可以被 S 类型的对象替换。YieldClass 类型的对象可能是 子类 BaseClass 的 em>,它不是 BaseClass子类型,因为 BaseClass(以及 ReturnClass)中的方法 do_something_1 返回一个 int 而方法 @987654344 @ in YieldClass 返回一个生成器。 YeldClass 类违反了其基类的约定,并且是非常糟糕的面向对象设计。简而言之,这不再是模板方法模式,而是所谓的反模式

    【讨论】:

    • 是的,我认为我违反了它。但我正在考虑一种重用相同过程的方法。唯一的问题是一个像生成器函数,一个像普通的返回函数
    • 你必须重新定义模板方法;这并不完全是“重用相同的过程”。您可能应该重新提出这个问题,提供更多关于您真正想要做的事情的实际细节,也许相同的模式或模式的组合可以解决它。
    【解决方案2】:

    正如 Booboo 所解释的,您确实在这里违反了基类合同。而且您还在复制代码,这肯定会导致微妙的错误。作为更一般的答案,如果您发现自己覆盖了模板方法,那么您遇到了设计问题。

    在您当前的示例中(nb:我假设您的真实代码产生多个值 - 否则我真的不明白这一点),更好的解决方案是通过向基础添加新的责任来解决这一需求直接类(当然在不同的方法名称下)并根据新的生成器方法重新实现当前的基本do_something() 方法,即:

    class BaseClass(ABC):
    
        def iter_do_something(self):
            x = self.do_step1()
            y = self.do_step2()
            yield x + y
    
        def do_something(self):
            # in real life - assuming 'iter_do_something' yields more
            # than one single value - you would just return the whole list
            # instead of it's first element.
            return list(self.iter_do_something())[0]
    
        @abstractmethod
        def do_step1(self):
            pass
    
        @abstractmethod
        def do_step2(self):
            pass
    

    【讨论】:

    • 是的,但它们确实遵循相同的原则..只有一个是屈服,另一个是正常的返回类......我可以在这里做一个选项吗?
    • 上面的例子两者都有——你只需要使用一种方法或另一种方法。具有返回值或生成器的相同方法破坏了 Liskov 的替换原则(您的 YieldClass 不是 BaseClass 的正确子类型,您不能用一个替换另一个),并且复制模板方法逻辑是违反 DRY 的。现在,如果您发布了一个更能代表您的实际用例的示例(产生一个单一值的意义何在???)并解释了您尝试这样做的原因,您可能会得到更好的答案...
    猜你喜欢
    • 2011-07-24
    • 2015-02-14
    • 1970-01-01
    • 1970-01-01
    • 2020-05-13
    • 2012-09-15
    • 1970-01-01
    • 2018-10-22
    • 2014-10-13
    相关资源
    最近更新 更多