【问题标题】:Issue with python class hierarchypython类层次结构问题
【发布时间】:2011-01-18 15:33:48
【问题描述】:

我有一个类层次结构:

class ParentClass:

    def do_something(self):
        pass # child classes have their own implementation of this

class ChildClass1(ParentClass):

    def do_something(self):
        <implementation here>

class ChildClass2(ParentClass):

    def do_something(self, argument_x):
        <implementation here>

class ChildClass3(ParentClass):

    def do_something(self, argument_y):
        <implementation here>

这里有两个问题:

  • 方法 do_something() 在子类中有不同的接口:它在子类 2 和 3 中接受参数,但在子类 1 中没有参数
  • do_something() 的参数有不同的名称,以强调它们在子类 2 和 3 中具有不同的含义。这将在下面通过使用示例变得更加清晰

这是如何使用这些类的:

有一个返回实例的工厂类:

class ChildFactory:

    def get_child(self, argument):
        if argument == '1':
            return ChildClass1()
        elif argument == '2':
            return ChildClass2()
        elif argument == '3':
            return ChildClass3()

稍后在代码中:

...
# pseudocode, not python
child_type = ? # can have values '1', '2' or '3' at this moment
var1 = 1
var2 = 'xxx'
# var1 and var2 have different types, just to emphasize the difference in their
# meaning when being passed as arguments to do_something()
# this was mentioned above (the second problem)
child = ChildFactory.get_child(child_type)
if child is an instance of ChildClass1, child.do_something() is called
if child is an instance of ChildClass2, child.do_something(var1) is called
if child is an instance of ChildClass3, child.do_something(var2) is called
# end of pseudocode

问题:

  1. 上面提到的两个问题是不是设计不好?如果是,那么设计层次结构的正确方法是什么?
  2. 如何在python中统一编写伪代码sn-p?主要问题是避免在每个特定情况下使用巨大的 if/else 语句,因为它会使 ChildFactory.get_child() 中的 if/else 语句加倍

【问题讨论】:

    标签: python oop


    【解决方案1】:

    具有相同名称和不同参数的方法是代码异味。

    “方法do_something()在子类中有不同的接口:它在子类2和3中接受参数,但在子类1中没有参数”

    你没有说为什么。有两个很好的理由

    • 子类 1 具有默认值。

    • 子类 2 忽略该值。

    几乎任何其他原因都表明do_something 确实不同,应该有不同的名称。

    如果子类 1 具有默认值,则只需在方法函数的参数中显式编写默认值即可。

    class ChildClass1( ParentClass ):
        def do_something( argument_x= None )
            ....
    

    如果子类 1 忽略该值,则只需忽略该值。不要站在你的头上忽略一个值。

    class ChildClass1( ParentClass ):
        def do_something( argument_x )
            return True
    

    不碰巧使用所有参数值的多态方法函数没有什么神奇之处。

    “do_something() 的参数有不同的名称,以强调它们在子类 2 和 3 中具有不同的含义。”

    这只是糟糕的设计。您不能使用具有不同参数名称的相同方法函数,因为它们执行不同的操作。

    具有相同的方法函数名称是错误的。如果它们是相似事物的不同实现,那么参数将具有本质上相同的含义。

    如果它们实际上做了不同的事情,那么你就没有多态性,你不应该给这些方法起相同的名字。

    当两个类中的方法做根本不同的事情时——需要具有不同名称的不同参数以使这一点显而易见——这些方法不能具有相同的名称。如果名称没有描述该方法的实际作用,则该名称不再具有意义。

    注意

    顺便说一句,由于鸭子类型,您的代码将在 Python 中工作。只要方法名称匹配,参数类型甚至不必接近匹配。然而,这确实是一个糟糕的设计,因为方法之间的本质差异是如此巨大。

    【讨论】:

    • 考虑实现卡片的类。卡片类有一个接受参数(另一张卡片)并返回布尔值的方法。是的,如果它可以击败那张牌。小丑可以击败任何其他牌,它的方法将始终返回 True 并且不需要输入参数。不确定这个例子有多合适。关键是在我的示例中 ChildClass1.self() 根本没有默认值。
    • @Alex_coder:Joker still 的卡片比较接受一个参数。它只是不使用参数。 def beats( self, aCord): return True。卡片比较是方法的含义,即使它忽略了参数。
    • 感谢您的清晰解释。你关于含义的陈述是目前最好的 AHA 候选人。这是否意味着在这种情况下,不将 pylint 警告视为命令是明智的,只是让它们保持沉默并让常识占上风?我可以看到优势:如果我故意忽略小冗余问题,代码会变得统一。
    • @Alex_coder:“小冗余问题”?你的意思是未使用的参数警告?是的,你必须保持沉默才能拥有适当的多态性。
    • 有趣的是@S.Lott 说了四次同样的事情,但没有人解释为什么它不好。他只是重复这是不好的,他们不应该有相同的名字等等。
    【解决方案2】:

    像这样的抽象问题很难回答。如果我们知道您要解决什么问题,回答会容易得多。我可以告诉你,看到以下内容通常是一个不好的迹象:

    if isinstance(obj, cls1):
        ...
    elif isinstance(obj, cls2):
        ...
    

    通常,这意味着您应该定义一个新方法而不是使用 if/elif。在 Python 中,如果您愿意,可以在类定义之外定义方法,如果这有帮助的话。如果两个可互换的类具有相同名称但采用不同数量的参数的方法,这通常也是一个不好的迹象,这意味着这些类并不是真正可互换的。不同的方法应该采用相同的参数,或者它们应该具有不同的名称。也没有必要定义从不调用的方法,例如 ParentClass 中的 do_something ——这是您在从 C++/Java/C# 开始使用 Python 的程序员中看到的。

    【讨论】:

    • 我还看到了使 do_something() 接受两个具有默认值的参数的选项,但是像 pylint 这样的代码检查器会抱怨没有使用传递给函数的参数。因此进退两难。
    • *args, **kwargs 可用于变量参数计数,但我同意你的观点,这闻起来像是糟糕的设计。
    • 应该有一种方法可以告诉 pylint 您打算不使用这些变量。不要为了让 pylint 高兴而使用奇怪的控制结构。
    • +1 因为在 Python 中关注特定实例名称通常是一个不好的迹象...
    【解决方案3】:

    如果要互换使用,它们应该具有相同的界面。对于一个方法,这意味着相同数量的参数具有相同的含义和相同的名称以相同的顺序。如果它们的行为不完全相同,只需给它们起不同的名称并使其看起来可以互换。

    【讨论】:

      【解决方案4】:

      您可以这样做,以使签名相同:

      class ParentClass:
          pass
      
      class ChildClass1(ParentClass):
      
          def do_something(self, **kwargs):
              <implementation here>
      
      class ChildClass2(ParentClass):
      
          def do_something(self, **kwargs):
              argument_x = kwargs[argument_x]
              <implementation here>
      
      class ChildClass3(ParentClass):
      
          def do_something(self, **kwargs):
              argument_y = kwargs[argument_y]
              <implementation here>
      

      工厂可能只是一个字典:

      childfactory = {1:ChildClass1, 2:ChildClass2, 3:ChildClass3}
      

      然后:

      ...
      # pseudocode, not python
      child_type = ? # can have values '1', '2' or '3' at this moment
      var1 = 1
      var2 = 'xxx'
      # var1 and var2 have different types, just to emphasize the difference in their
      # meaning when being passed as arguments to do_something()
      # this was mentioned above (the second problem)
      child = childfactory[child_type]()
      child.do_something(argument_x=val1, argument_y=var2)
      # end of pseudocode
      

      【讨论】:

        猜你喜欢
        • 2011-09-06
        • 1970-01-01
        • 2023-02-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多