Проблема с иерархией классов Python - PullRequest
3 голосов
/ 19 февраля 2010

У меня есть иерархия классов:

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? Основная задача - избегать использования огромного оператора if / else для каждого конкретного случая, поскольку он удваивает оператор if / else из ChildFactory.get_child ()

Ответы [ 4 ]

10 голосов
/ 19 февраля 2010

Методы с одинаковыми именами и разными аргументами - это запах кода.

"Метод do_something () имеет разные интерфейсы в подклассах: он принимает аргумент в дочерних классах 2 и 3, но не имеет аргумента в дочернем классе 1"

Ты не скажешь почему. Есть две веские причины, по которым

  • дочерний класс 1 имеет значение по умолчанию.

  • дочерний класс 2 игнорирует значение.

Почти любая другая причина указывает, что do_something действительно отличается и должно иметь другое имя.

Если дочерний класс 1 имеет значение по умолчанию, то просто явно укажите значение по умолчанию в аргументах функции метода.

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

Если дочерний класс 1 игнорирует значение, тогда просто игнорируйте значение. Не стой на своей голове, чтобы игнорировать vlaue.

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

Нет ничего волшебного в том, что функция полиморфного метода не использует все значения аргументов.

«аргументы do_something () имеют разные имена, чтобы подчеркнуть, что они имеют разные значения в дочерних классах 2 и 3».

Это просто плохой дизайн. Вы не можете иметь одну и ту же функцию метода с разными именами аргументов, потому что они делают разные вещи.

Наличие одного и того же имени функции метода просто неверно. Если это разные реализации схожих вещей, то аргументы будут иметь по существу одинаковые значения.

Если они на самом деле делают разные вещи, то у вас нет полиморфизма, и вы не должны давать этим методам одинаковые имена.

Когда методы в двух классах делают принципиально разные вещи - требуя разных аргументов с разными именами, чтобы сделать это очевидным - эти методы не должны иметь одинаковые имена. Имя перестает иметь значение, если оно не описывает, что на самом деле делает метод.

Примечание

Ваш код, кстати, будет работать на Python из-за утки. Пока имена методов совпадают, типы аргументов даже не должны приближаться к соответствию. Тем не менее, это действительно плохой дизайн, потому что существенные различия между методами очень велики.

2 голосов
/ 19 февраля 2010

На такие абстрактные вопросы очень сложно ответить. Было бы гораздо проще ответить, если бы мы знали, какую проблему вы пытаетесь решить. Я могу сказать вам, что обычно это плохой знак, чтобы увидеть что-то вроде:

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

Обычно это означает, что вы должны определять новый метод вместо использования if / elif. В Python вы можете определять методы вне определения класса, если хотите, если это помогает. Также обычно это плохой признак, если два взаимозаменяемых класса имеют методы с одинаковым именем, но принимают разное количество аргументов, это означает, что классы не являются действительно взаимозаменяемыми. Либо разные методы должны принимать одинаковые аргументы, либо они должны иметь разные имена. Также нет необходимости определять методы, которые никогда не вызываются, например do_something в ParentClass - это то, что вы видите в программистах, которые приходят на Python из C ++ / Java / C #.

1 голос
/ 19 февраля 2010

Если вещи должны использоваться взаимозаменяемо, они должны иметь одинаковый интерфейс. Для метода это означает одинаковое количество аргументов с одинаковыми значениями и одинаковыми именами в одинаковом порядке. Если они не ведут себя одинаково, просто дайте им разные имена и создайте впечатление, что они взаимозаменяемы.

0 голосов
/ 19 февраля 2010

Вы можете сделать это, чтобы подписи были такими же:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...