Рефакторинг огромного класса Python с использованием Inheritance для создания Composition

Я построил pygame игра несколько лет назад. Он работал, но не был лучшим стилем кодирования и имел много классических запахов кода. Я недавно поднял это и пытаюсь рефакторинг с большей дисциплиной на этот раз.

Один большой запах кода был в том, что у меня был огромный класс, GameObject, который унаследовал от pygame.sprite.DirtySprite который имел много кода, связанного с:

  • различные способы перемещения спрайта
  • различные способы анимации спрайта
  • различные способы взрыва спрайта
  • и т.п.

Сумасшедший, я думал о способах поведения спрайтов, дублирование кода складывалось, и изменения становились все труднее. Итак, я начал разбивать функциональность на множество небольших классов и затем передавать их при создании объекта:

class GameObject(DirtySprite):
    def __init__(initial_position, mover_cls, imager_cls, exploder_cls):
        self.mover = mover(self, initial_position)
        self.imager = imager(self)
        self.exploder = exploder(self)

...
spaceship = GameObject(pos, CrazyMover, SpaceshipImager, BasicExploder)

Поскольку я включал в эти вспомогательные классы все больше и больше кода, он определенно стал лучше, более гибким и имел меньше дублирования. Однако для каждого типа вспомогательных классов число параметров увеличивается и увеличивается. Создание спрайтов стало рутиной, а код был безобразным. Итак, во время другого рефакторинга я создал несколько действительно маленьких классов для создания композиции:

class GameObjectAbstract(MoverAbstract, ImagerAbstract, \
                         ExploderAbstract, DirtySprite):
    def __init__(self, initial_position):
        ...
...
class CrazySpaceship(CrazyMover, SpaceshipImager, BasicExploder, GameObjectAbstract):
    pass  # Many times, all the behavior comes from super classes

...
spaceship = CrazySpaceship(pos)

Мне больше нравится этот подход. Это общий подход? Кажется, что те же преимущества имеют то, что вся логика разбита на маленькие классы, но создание объектов намного чище.

Однако этот подход не такой динамичный. Я не могу, например, выбрать новый mashup во время выполнения. Тем не менее, это не то, что я действительно делал. Хотя я делаю много гибридных приложений, кажется, что они статически определены с помощью операторов класса.

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

Есть ли другой шаблон, который я должен использовать?

1 ответ

Решение

Это нормально, если вы можете достаточно хорошо отделить поведение -

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

Если вы используете Python super правильно, это может быть лучшим подходом. (Если вам удается создавать свои игровые объекты, в основном просто определяя имя класса и классы миксина, которые он использует, это на самом деле очень хороший подход)

Кстати, если вы когда-нибудь захотите создать новые классы во время выполнения с помощью этого метода, это также возможно - просто используйте вызов type создать новый класс, а не class заявление:

class CrazySpaceship(CrazyMover, SpaceshipImager, BasicExploder, GameObjectAbstract):
    pass  # Many times, all the behavior comes from super classes

Это просто эквивалентно в Python:

CrazySpaceShip = type('CrazySpaceShip', (CrazyMover, SpaceshipImager, BasicExploder, GameObjectAbstract), {})

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

Другие вопросы по тегам