ООП: полиморфизм, наследование, инкапсуляция в Python

Объектно ориентированное программирование (ООП) – базируется на трех парадигмах: полиморфизм, инкапсуляция, наследование. Эти слова вызывают ужас в глазах у многих новичков, но на самом деле не все так страшно и сложно. Разберемся в основной идее ООП и почему надо перестать бояться им пользоваться. Начнем с полиморфизма и его определения «один интерфейс — много реализаций». О чем это?

Полиморфизм. Наглядное и краткое объяснение в Python

Абстрогируемся на секунду от мира программирования и обратимся в мир реальный, в котором есть очень много объектов, которые имеют много общего между собой. Например птицы, собаки, кошки или самолеты, корабли, автомобили. Птицы имеют виды, кошки и собаки имеют породы, самолеты, корабли и автомобили имеют назначение транспорт. Т.е. мы можем отнести воробья и ворону к классу птицы, автомобили и корабли к классу транспорт, кошек и собак к классу домашние животные.

Для наглядности возьмем автомобили. У всех автомобилей есть много общего: марка, модель, год выпуска, пробег, комплектация, тип двигателя и все эти общие атрибуты имеют различные комбинации значений. Таким образом, мы можем вывести «класс автомобиль», это абстрактный класс у которого есть атрибуты:

  • марка
  • модель
  • цвет
  • год выпуска

ограничимся этими атрибутами. Так при чем тут полиморфизм? Тут начинается самое интересное, если нам пришлось бы каждый раз создавать отельную функцию для отдельной марки, модели и т.д., то это стало бы утопией. Полиморфизм позволяет нам реализовать один интерфейс (класс автомобиль), который принимает какие-либо данные и обрабатывает их одинаково. Еще наглядней:

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

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

bmw = Car('BMW', 'M5 E60', "Black", 2010)
audi = Car('Audi', 'RS6', 'Red', 2020)
lada = Car('Lada', 'Vesta', 'Green', 2022)

Теперь у нас есть три объекта класса автомобиль, у которых есть свои отличные друг от друга значения атрибутов, которые мы можем одинаково обрабатывать и хранить при помощи одного интерфейса class Car.

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

Инкапсуляция в Python

Основная идея инкапсуляции — это сокрытие данных, которые находятся внутри класса, а конкретней сокрытие методов и атрибутов класса. Есть три уровня сокрытия данных:

  1. public — публичные доступны внутри и во вне класса
  2. protected — защищенные доступны внутри класса и в классах наследниках.
  3. private — приватные доступны только внутри класса.

Возвращаясь сразу к полиморфизму, я сказал, что объекты класса имею атрибуты и методы самого класса, но это не совсем истина, так как доступа к private у нас не будет, а вот к protected будет и тут есть один важный момент. В Python нет жесткого запрета на вызов метода сокрытых данных уровня protected просто между программистами де-факто есть договоренность не использовать методы и атрибуты класса, которые начинаются с одного символа нижнего подчеркивания «_protected».

class Car:
    def __init__(self, brand, model, color, year):
        self.brand = brand # public
        self._model = model # protected
        self.__color = color # private
        self.year = year

    def _show_model(self):
        print(self._model)

bmw = Car('BMW', 'M5 E60', "Black", 2010)
audi = Car('Audi', 'RS6', 'Red', 2020)
lada = Car('Lada', 'Vesta', 'Green', 2022)
print(lada.brand) # сработает
print(audi._model) # сработает, но это дурной тон
print(bmw.__color) # вылетит ошибка AttributeError: 'Car' object has no attribute '__color'

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

Наследование в ООП на примере Python кода

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

Все очень просто, у нас уже есть реализованный класс, который принимает информацию об автомобиле и что-то с ней может сделать. Напишем новый класс и унаследуем его:

class Car: 
    def __init__(self, brand, model, color, year): 
        self.brand = brand 
        self.model = model 
        self.color = color 
        self.year = year 
    
    def print_info(self): 
        print(f'Марка: {self.brand}\n' f'Модель: {self.model}\n' f'Год выпуска: {self.year}\n' f'Цвет: {self.color}') 
        
class bwm(Car): 
    pass 

bmw = bwm('BMW', 'M5E60', 'Red', 2010) 
bmw.print_info()

И мы получим вывод:

Марка: BMW
Модель: M5E60
Год выпуска: 2010
Цвет: Red

Что тут происходит? Мы реализовали в родительском классе метод, который печатает атрибуты по вызову, создали второй класс и унаследовали этот метод от родителя class bwm(Car) теперь класс bmw ведет себя также как и класс Car, но дополнительно мы можем написать внутри класса bmw свои методы. Это и есть основная суть наследования, переиспользование кода (code reuse).

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