Пять основных продвинутых концепций объектно-ориентированного программирования, интуитивно объясненных

От инкапсуляции к абстракции

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

В этой статье будут рассмотрены следующие концепции объектно-ориентированного программирования с диаграммами, аналогиями и кодом:

  • Основы и экземпляры
  • Наследование
  • Состав
  • Инкапсуляция
  • Полиморфизм
  • Абстракция

Код предоставлен на языке Python.

Основы и реализация

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

Рассмотрим тогда сотрудника по имени Джек, который получает 30 долларов в час и только что получил работу (остаток выплат равен 0). Его можно было представить с помощью шаблона Employee, заполнив некоторые из его уникальных переменных. Создание объекта на основе «шаблона» или, более формально, класса называется инстанциацией.

Jack - это объект, поэтому его методы и переменные могут быть вызваны. Если мы вызовем Jack.name, мы получим строку ‘Jack’. Если Jack.pay_money() запущен, баланс объекта будет обновлен с соответствующим платежом.

Наследование

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

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

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

При наследовании мы создаем новый класс, но наследуем переменные и атрибуты другого класса. Затем мы можем либо изменить существующие функции с тем же именем, либо добавить новые.

В этом примере класс Employee называется родительским классом, а класс EmployeeTips называется дочерним или производным классом.

Через создание экземпляра мы можем использовать этот класс для Джилл. Обратите внимание, что даже несмотря на то, что внутренние переменные не были явно указаны, они были загружены посредством наследования. Те же параметры, что и в классе Employee, применимы и к классу EmployeeTips.

Когда вызывается Jill.pay_money(tips=30), что означает, что она заработала 30 долларов в виде чаевых, ее баланс увеличивается на 50 долларов, поскольку обновленный класс выплачивает как чаевые, так и базовый платеж в размере 20 долларов.

Состав

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

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

Чтобы использовать атрибуты и переменные этого класса в классе Employee, мы можем просто указать класс Information как одну из его переменных.

Поскольку класс Information является классом внутри класса, его свойства все равно необходимо вызывать из него. Чтобы распечатать информацию, нужно позвонить Jack.information.print_report(), предполагая, что Джек уже был инициализирован.

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

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

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

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

Это работает, но есть одна проблема: вместо использования безопасного метода, такого как Jack.confirm(‘i_am_jack2016’), кто-то может напрямую запустить Jack.pay_money(), что позволит обойти подтверждение пароля.

Чтобы предотвратить это, нам нужно заблокировать части объекта от создания экземпляра или использовать инкапсуляцию. В этом случае функцию класса .pay_money() необходимо заблокировать, чтобы ее нельзя было вызывать извне, только внутри объекта через другие внутренние функции, такие как confirm(). Для этого перед именем и после имени помещаются два символа подчеркивания, например __pay_money__. Когда кто-то пытается получить доступ к функции извне, возникает ошибка атрибута (‘Employee’ object has no attribute ‘pay_money’).

Инкапсуляция используется по нескольким причинам:

  • Обеспечивает безопасность приложений.
  • Обеспечивает удобство работы пользователей, не раскрывая сложностей серверной части.
  • Улучшает процесс разработки, предотвращая возможные лазейки из-за отсутствия ограничений по объему.

Полиморфизм

Полиморфизм - важный аспект классов, он позволяет ссылаться на один и тот же метод в нескольких классах. Слово «полиморфизм» буквально означает «состояние, которое встречается в нескольких различных формах».

Например, рассмотрим два объекта cat и dog. У них обоих есть функция с одинаковым названием make_sound(), которая заставляет cat печатать «meow!», а dog - «woof!». Из-за полиморфизма следующий оператор является допустимым и выводит «meow! woof!».

Кроме того, полиморфизм работает с другими объектами, такими как параметры в функциях: (при условии, что .make_noise() возвращает строку вместо печати)

Абстракция

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

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

Давайте переосмыслим нашу предыдущую модель создания экземпляров сотрудников. Давайте создадим абстрактную идею Employee.

Затем, основываясь на этой абстрактной идее Сотрудника, мы можем создать класс менеджера с более подробной информацией о том, что такое абстрактный метод посредством наследования. do_taxes() и oversee_operations() являются гипотетической заменой того, что менеджер может делать при вызове do job.

Отсюда можно создавать экземпляры объектов. Например, если бы Джек был менеджером, мы бы сказали Jack = Manager(name=’Jack’). Поскольку класс Employee является абстракцией, не имеет смысла делать что-то вроде Jack = Employee(), поскольку он содержит абстрактный метод do job, который не определен.

Спасибо за прочтение!