Что такое полиморфизм в Python? Объяснено на примере

Полиморфизм - это принцип объектно-ориентированного программирования (ООП), который помогает вам создавать высококачественное, гибкое, поддерживаемое, повторно используемое, тестируемое и читаемое программное обеспечение. Если вы планируете работать с объектно-ориентированным программным обеспечением, крайне важно понимать полиморфизм.

Что такое полиморфизм?

Слово "полиморфизм" происходит от греческого и означает "имеющий множество форм".:

  • Поли = много
  • Морф = формы

В программировании полиморфизм - это способность объекта принимать различные формы.

Ключевым преимуществом полиморфизма является то, что он позволяет нам писать более универсальный и многократно используемый код. Вместо того, чтобы писать отдельную логику для разных классов, мы определяем общие модели поведения в родительском классе и позволяем дочерним классам переопределять их по мере необходимости. Это устраняет необходимость в чрезмерных проверках if-else, делая код более удобным в обслуживании и расширяемым.

Фреймворки MVC, такие как Django, используют полиморфизм, чтобы сделать код более гибким. Например, Django поддерживает различные базы данных, такие как SQLite, MySQL и PostgreSQL. Обычно для взаимодействия с каждой базой данных требуется разный код, но Django предоставляет единый API базы данных, который работает со всеми базами данных. Это означает, что вы можете писать один и тот же код для операций с базой данных, независимо от того, какую базу данных вы используете. Итак, если вы начинаете проект с SQLite, а затем переходите на PostgreSQL, вам не нужно будет переписывать большую часть вашего кода благодаря полиморфизму.

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

(Кстати, если вы лучше разбираетесь в видео, посмотрите мое видео на YouTube о полиморфизме в Python.)

Во-первых, пример без полиморфизма:

class Car:
    def __init__(self, brand, model, year, number_of_doors):
        self.brand = brand
        self.model = model
        self.year = year
        self.number_of_doors = number_of_doors

    def start(self):
        print("Car is starting.")

    def stop(self):
        print("Car is stopping.")
class Motorcycle:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year

    def start_bike(self):
        print("Motorcycle is starting.")

    def stop_bike(self):
        print("Motorcycle is stopping.")

Допустим, мы хотим создать список транспортных средств, затем просмотреть его и выполнить проверку каждого транспортного средства:

# Create list of vehicles to inspect
vehicles = [
    Car("Ford", "Focus", 2008, 5),
    Motorcycle("Honda", "Scoopy", 2018),
]

# Loop through list of vehicles and inspect them
for vehicle in vehicles:
    if isinstance(vehicle, Car):
        print(f"Inspecting {vehicle.brand} {vehicle.model} ({type(vehicle).__name__})")
        vehicle.start()
        vehicle.stop()
    elif isinstance(vehicle, Motorcycle):
        print(f"Inspecting {vehicle.brand} {vehicle.model} ({type(vehicle).__name__})")
        vehicle.start_bike()
        vehicle.stop_bike()
    else:
        raise Exception("Object is not a valid vehicle")

Обратите внимание на уродливый код внутри цикла for! Поскольку vehicles - это список объектов любого типа, нам нужно выяснить, с каким типом объектов мы имеем дело внутри каждого цикла, прежде чем мы сможем получить доступ к какой-либо информации об объекте.

Этот код будет становиться все более уродливым по мере того, как мы будем добавлять новые типы транспортных средств. Например, если бы мы расширили нашу кодовую базу, включив в нее новый класс Plane, то нам потребовалось бы изменить (и, возможно, нарушить) существующий код - нам пришлось бы добавить еще одну условную проверку в цикле for для planes.

Представляем: Полиморфизм...

И автомобили, и мотоциклы являются транспортными средствами. У них есть некоторые общие свойства и методы. Итак, давайте создадим родительский класс, содержащий эти общие свойства и методы:

Родительский класс (или "суперкласс"):

class Vehicle:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year

    def start(self):
        print("Vehicle is starting.")

    def stop(self):
        print("Vehicle is stopping.")

Автомобили и мотоциклы теперь могут наследоваться от Vehicle. Давайте создадим дочерние классы (или "подклассы") суперкласса Vehicle:

class Car(Vehicle):
    def __init__(self, brand, model, year, number_of_doors):
        super().__init__(brand, model, year)
        self.number_of_doors = number_of_doors

    # Below, we "override" the start and stop methods, inherited from Vehicle, to provide car-specific behaviour

    def start(self):
        print("Car is starting.")

    def stop(self):
        print("Car is stopping.")
class Motorcycle(Vehicle):
    def __init__(self, brand, model, year):
        super().__init__(brand, model, year)

    # Below, we "override" the start and stop methods, inherited from Vehicle, to provide bike-specific behaviour

    def start(self):
        print("Motorcycle is starting.")

    def stop(self):
        print("Motorcycle is stopping.")

Автомобиль и мотоцикл являются дополнением к транспортному средству, поскольку они являются транспортными средствами. Но какой смысл в том, что и автомобиль, и мотоцикл являются дополнением к транспортному средству, если они собираются реализовать свои собственные версии методов start() и stop()? Посмотрите на приведенный ниже код:

# Create list of vehicles to inspect
vehicles = [Car("Ford", "Focus", 2008, 5), Motorcycle("Honda", "Scoopy", 2018)]

# Loop through list of vehicles and inspect them
for vehicle in vehicles:
    if isinstance(vehicle, Vehicle):
        print(f"Inspecting {vehicle.brand} {vehicle.model} ({type(vehicle).__name__})")
        vehicle.start()
        vehicle.stop()
    else:
        raise Exception("Object is not a valid vehicle")

В этом примере:

  • У нас есть список транспортных средств, содержащий экземпляры как автомобиля, так и мотоцикла.
  • Мы просматриваем каждое транспортное средство в списке и проводим общий осмотр каждого из них.
  • Процесс техосмотра включает в себя запуск автомобиля, проверку его марки и модели, а затем его остановку.
  • Несмотря на то, что транспортные средства относятся к разным типам, полиморфизм позволяет нам рассматривать их все как экземпляры базового класса транспортных средств. Конкретные реализации методов start() и stop() для каждого типа транспортных средств вызываются динамически во время выполнения на основе фактического типа каждого транспортного средства.

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

Это демонстрирует, как полиморфизм позволяет писать код более универсальным и гибким образом, упрощая расширение и обслуживание по мере добавления в систему новых типов транспортных средств.

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

class Plane(Vehicle):
    def __init__(self, brand, model, year, number_of_doors):
        super().__init__(brand, model, year)
        self.number_of_doors = number_of_doors

    def start(self):
        print("Plane is starting.")

    def stop(self):
        print("Plane is stopping.")
# Create list of vehicles to inspect
vehicles = [
    Car("Ford", "Focus", 2008, 5),
    Motorcycle("Honda", "Scoopy", 2018),

    ########## ADD A PLANE TO THE LIST: #########

    Plane("Boeing", "747", 2015, 16),

    ############################################
]

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

Вывод

Полиморфизм позволяет клиентам одинаково обращаться с различными типами объектов. Это значительно повышает гибкость программного обеспечения и его ремонтопригодность, поскольку можно создавать новые классы без необходимости изменять (часто путем добавления дополнительных блоков if/else if) существующий рабочий и протестированный код.

Дальнейшее обучение

Полиморфизм связан со многими другими принципами объектно-ориентированного программирования, такими как внедрение зависимостей и принцип SOLID "открыто-закрыто". Если вы хотите освоить ООП, ознакомьтесь с моим курсом на Udemy:

  • ООП на Python: Объектно-ориентированное программирование от новичка до профессионала

Если вы предпочитаете книги видео, ознакомьтесь с моими книгами:

  • Amazon Kindle и книги в мягкой обложке –
  • Gumroad PDF ðŸ " –

Спасибо за чтение :)