ООП

Введение в объектно-ориентированное программирование: наследование

Предыдущая статья — Введение в объектно-ориентированное программирование: класс Blob и модульность.

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

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

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

Представьте, что вы создаете инструмент для других людей. Допустим, это класс «молоток». У вашего молотка будут такие атрибуты как ручка, и, скорей всего, металлическая головка для забивания гвоздей. Это минимально жизнеспособный продукт (MVP), и его вполне можно отгружать в таком виде.

У вас конечно могут возникнуть и другие идеи. Например, можно было бы сделать молоток с двумя сторонами для забивания гвоздей разного размера, или, может быть, создать какую-нибудь комбинированную головку, где одна сторона металлическая, а другая — резиновая!

Тут у вас может возникнуть соблазн реализовать свои идеи, потому что они кажутся вам разумными. Но вы сами не хотите этого делать. И вы решаете позволить клиентам изменять класс молотка, если они захотят, например, резиновый молоток (что будет эквивалентом покупки вместо этого резинового молотка). То есть, вы хотите просто выпустить самую простую версию своего продукта и посмотреть, что думают клиенты.

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

Наследование позволяет создавать суперпростые классы, а пользователи в дальнейшем могут их изменять. Если вы получаете достаточно запросов на что-то новое, вы можете подумать о добавлении этого, но сначала позвольте вашим клиентам поиграть с этим. Вернемся к нашему примеру с классом Blob и рассмотрим наследование от него:

class BlueBlob(Blob):
    pass

В этом классе ничего не происходит, кроме наследования от класса Blob. Класс Blob — это наш базовый или родительский класс. Его также можно назвать суперклассом. Мы не станем вам предлагать использовать все эти термины в других языках программирования, но в Python вы можете применять их все (вы заметите, что окружающие тоже так делают). Подобное многообразие терминов сложилось главным образом потому, что программисты, приходя в Python из других языков, приносят все эти термины с собой.

Новый класс BlueBlob, унаследованный от Blob, является нашим «дочерним» классом или подклассом. При наследовании от другого класса вы наследуете всё: все методы, включая и специальные.

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

def main():
    blue_blobs = dict(enumerate([BlueBlob(BLUE,WIDTH,HEIGHT) for i in range(STARTING_BLUE_BLOBS)]))
    red_blobs = dict(enumerate([BlueBlob(RED,WIDTH,HEIGHT) for i in range(STARTING_RED_BLOBS)]))

Мы просто используем новый класс. Результат должен быть таким же, как и раньше:

Обычной задачей может быть добавление нашего собственного метода __init__, но что произойдет, если мы попробуем это сделать?

class BlueBlob(Blob):
    def __init__(self):
        pass

Мы сразу получим ошибку:

TypeError: __init__() takes 1 positional argument but 4 were given

Ведь мы переопределили наш первоначальный метод __init__! И что, нам теперь нужно вручную переписать весь код из этого метода? Ничего подобного, в нашем распоряжении есть метод super()!

class BlueBlob(Blob):
    
    def __init__(self, color, x_boundary, y_boundary):
        super().__init__(color, x_boundary, y_boundary)
        self.color = BLUE

Теперь у нас все кляксы этого класса будут синими.

Вызов метода super() позволяет нам динамически обращаться к базовому классу. Мы также могли бы написать следующий код:

class BlueBlob(Blob):
    
    def __init__(self, color, x_boundary, y_boundary):
        Blob.__init__(self, color, x_boundary, y_boundary)
        self.color = BLUE

Но у такой записи есть недостаток: мы быстро столкнемся с большим количеством проблем при множественном наследовании. Некоторое время назад метод super() в Python считался довольно спорным, но Раймонд Хеттингер сделал отличный доклад, с которым вы обязательно должны ознакомиться, под названием Super considered super! Он достаточно длинный, но его изучение поможет вам понять метод super() в Python. К тому же, Раймонд Хеттингер превосходный оратор.

Мы также можем добавлять новые методы, например следующим образом:

class BlueBlob(Blob):
    
    def __init__(self, color, x_boundary, y_boundary):
        Blob.__init__(self, color, x_boundary, y_boundary)
        self.color = BLUE

    def move_fast(self):
        self.x += random.randrange(-5,5)
        self.y += random.randrange(-5,5)

Затем в методе draw_environment мы можем заменить blob.move () на blob.move_fast ():

def draw_environment(blob_list):
    game_display.fill(WHITE)

    for blob_dict in blob_list:
        for blob_id in blob_dict:
            blob = blob_dict[blob_id]
            pygame.draw.circle(game_display, blob.color, [blob.x, blob.y], blob.size)
            blob.move_fast()
            blob.check_bounds()
            
    pygame.display.update()

Ilyaragalin

Recent Posts

Сборка мусора в Python: ключевые концепции и механизмы

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

6 дней ago

Круговой импорт в Python и как его избежать

Как возникает круговой импорт? Эта ошибка импорта обычно возникает, когда два или более модуля, зависящих…

2 недели ago

Библиотека tqdm: визуализация прогресса выполнения скриптов Python

Вы когда-нибудь оказывались в ситуации, когда скрипт на Python выполняется очень долго и вы задаетесь…

3 недели ago

Символы новой строки в Python

В этом руководстве мы разберем все, что нужно знать о символах перехода на новую строку…

2 месяца ago

if __name__ == «__main__» в Python: полное объяснение

Блок if __name__ == "__main__" в Python позволяет определить код, который будет выполняться только при…

2 месяца ago

Как писать модульные тесты для методов экземпляра в Python

Давайте разберем, как настроить модульные тесты для экземпляров классов. Мы напишем тесты для проверки функциональности…

4 месяца ago