Будущее объектно-ориентированного программирования: взгляд вперед / Skillbox Media
Функциональное программирование, возможно, недооценило преимущества ООП. Не стоит ли переосмыслить вашу позицию?
Содержание:
Основы Python: Бесплатный курс для всех уровней подготовки Четыре впечатляющих проекта для вашего портфолио Прямое взаимодействие с преподавателем: уникальная возможность для обучения
Узнать больше
Рея Мутафис
об авторе
Предприниматель, обладающий докторской степенью по философии, полученной в Сорбонне, а также MBA от CDI. Публикует статьи для таких изданий, как TheNextWeb, HP Enterprise и Built In.
В начале эры программирования, в 1960-х годах, вычислительные машины обладали ограниченными возможностями. В связи с этим возникла необходимость в эффективном распределении их ресурсов между задачами и данными.
Сложность заключалась в том, что в то время невозможно было эффективно обрабатывать большие объемы информации, не перегружая при этом компьютер до его максимальных возможностей. Если возникала необходимость решать множество различных задач, каждая из них могла оперировать лишь с ограниченным количеством данных, иначе вычисления затягивались бы на неопределённый срок.
В середине шестидесятых годов Алан Кэй выдвинул концепцию независимых мини-компьютеров, которые имели бы возможность обмениваться не просто данными, а именно сообщениями. Это подход позволил бы значительно повысить эффективность распределения вычислительных ресурсов.
от переводчика
Алан Кэй, обладая образованием в области математики и молекулярной биологии, провел параллель между компьютерами и живыми клетками. Его концепция заключалась в том, что независимо работающие программы (подобно клеткам) могли бы обмениваться сообщениями между собой. При этом внутреннее состояние программ оставалось бы закрытым для внешнего мира, реализуя принцип инкапсуляции.
Кэй делится своими воспоминаниями следующим образом: «Я воспринимал объекты как нечто вроде биологических клеток или отдельных компьютеров, соединенных в сеть, которые способны взаимодействовать лишь посредством сообщений».
Хотя сама идея была весьма новаторской, внедрение объектно-ориентированного программирования произошло не мгновенно, и лишь к 1981 году она начала набирать популярность. С этого времени как начинающие, так и опытные программисты все активнее осваивают ООП. В результате сегодня рынок переполнен специалистами в области объектно-ориентированного программирования.
Объектно-ориентированное программирование уже более сорока лет занимает лидирующие позиции в разработке программного обеспечения. Тем не менее, за последние годы наблюдается растущая волна критики в его адрес. Может ли оказаться, что новые технологии просто превзошли эту парадигму?
Обоснованность интеграции данных и методов: стоит ли это делать?
Суть объектно-ориентированного программирования заключается в довольно простом принципе: вы делите свою программу на отдельные, самодостаточные компоненты. Это означает, что вы группируете все данные и функции, необходимые для работы с этими данными, в одном месте.
Обратите внимание, что это касается исключительно концепции инкапсуляции, при которой данные и функции скрыты внутри объекта и недоступны извне, обеспечивая тем самым защиту. Взаимодействие с содержимым объекта возможно лишь через специальные сообщения, известные как методы доступа, включая геттеры и сеттеры.
В современном объектно-ориентированном программировании существуют и другие ключевые концепции, которые не были частью первоначального замысла. К ним относятся наследование и полиморфизм.
Наследование представляет собой возможность для программистов создавать подклассы, которые унаследуют все характеристики своего родительского класса. Эта идея появилась в 1976 году, через десять лет после возникновения объектно-ориентированного программирования.
Спустя десятилетие в объектно-ориентированное программирование вошел концепт полиморфизма. В общем смысле это означает, что метод или объект могут служить основой для создания других методов и объектов. Полиморфизм представляет собой более гибкую концепцию по сравнению с наследованием, так как позволяет новой сущности не просто унаследовать все характеристики исходного метода или объекта, но и изменять их в соответствии с потребностями.
Полиморфизм характеризуется тем, что, несмотря на наличие зависимости между двумя сущностями в исходном коде, вызываемая сущность выступает в роли своеобразного шаблона для реальных объектов, которые используются на практике. Это значительно упрощает задачу разработчиков, поскольку им не требуется заботиться о взаимозависимостях во время выполнения программы.
В целом, концепции наследования и полиморфизма можно встретить не только в рамках объектно-ориентированного программирования. Однако именно инкапсуляция данных и связанных с ними методов выделяет ООП среди других подходов. В эпоху, когда вычислительные мощности были значительно ограничены по сравнению с современными, эта идея стала поистине революционной.
Предположим, вы решили использовать класс, который ранее применяли в другом проекте. Каковы будут последствия, если базовый класс, от которого он наследуется, будет изменён?
Ваш код может оказаться под угрозой, даже если изменения коснулись лишь небольшой его части. Например, вчера все производные классы функционировали безупречно, а сегодня возникли серьезные сбои. Причиной этому стало незначительное исправление в базовом классе, которое, тем не менее, оказало разрушительное влияние на весь проект. Эта ситуация называется проблемой хрупкого или «незащищенного» базового класса.
Выбор повторного использования класса может показаться быстрым и рациональным решением, однако впоследствии это может привести к значительным затратам. То есть, чем активнее применяется наследование, тем сложнее становится поддерживать код в работоспособном состоянии.
Наследование можно охарактеризовать как деликатный и плавный процесс, при котором характеристики одного класса передаются другому. Но что делать, если возникает необходимость объединить качества двух различных классов?
К сожалению, осуществить это просто и изящно не удастся.
Рассмотрим, к примеру, класс Copier, который представляет собой устройство для копирования. Этот аппарат способен сканировать документы и затем воспроизводить их на новом чистом листе бумаги.
Вопрос о том, к какому подклассу должен принадлежать Copier — сканеру или принтеру — имеет важное значение для понимания его функциональности. Copier, как устройство, предназначенное для копирования документов, может иметь характеристики как сканера, так и принтера. Однако, учитывая его основную задачу, логичнее отнести его к подклассу принтера, поскольку копирующее устройство, как правило, включает в себя функции печати, что является ключевым аспектом его работы. С другой стороны, наличие сканера в функции копира также важно, так как без возможности сканирования оригиналов устройство не сможет выполнять свои основные функции. В итоге, решение о том, к какому подклассу отнести Copier, зависит от того, какие аспекты его работы считать первостепенными — печать или сканирование.
Не существует единственно правильного решения. Данная ситуация известна как проблема ромба. Хотя код и остается целым, она тем не менее часто вызывает разочарование у программистов.
Предыдущий вопрос касался того, кто является предком класса Copier. Я вас ввел в заблуждение с ответом: из данной ситуации есть элегантное решение. Сделаем Copier родительским классом, а Scanner и Printer — его дочерними классами, которые будут наследовать лишь часть его свойств. Вот и все!
Это действительно интересный вопрос. Однако, представьте, что у нас есть устройство Copier, которое печатает только в чёрно-белом режиме, и в то же время Printer, способный производить цветные отпечатки. В данном случае, не кажется ли вам, что Printer является более универсальным термином по сравнению с Copier? Также возникает вопрос, что делать, если Printer требует подключения к Wi-Fi, а Copier может работать без него?
С увеличением количества характеристик в классе становится труднее определить корректную иерархию. В данном случае речь идет о свойствах таких классов, как Copier и Printer, которые содержат как общие, так и специфические элементы. Если попытаться организовать эти классы в линейную иерархию наследования, особенно в рамках крупного и сложного проекта, это может привести к возникновению различных трудностей. Проблема заключается в том, что не удается однозначно выбрать родительский класс среди Copier, Printer и Scanner.
Когда вы сталкиваетесь с вопросами иерархии, у вас есть возможность рассмотреть вариант: давай сосредоточимся на объектно-ориентированном программировании без иерархических структур. Можно просто применять коллекции свойств, наследуя их и внося изменения или дополнения по мере необходимости. Хотя такой подход может показаться несколько запутанным, он, тем не менее, остается вполне осуществимым, не так ли?
К сожалению, это не так. Здесь появляется другая проблема. Главная идея инкапсуляции заключается в том, чтобы более эффективно управлять данными, обеспечивая их защиту друг от друга. Без четкой иерархии достичь этого невозможно.
Предположим, что объект A должен вступить во взаимодействие с объектом B. При этом не имеет значения, в каких именно отношениях они находятся, важно лишь то, что A не является прямым потомком B.
Объект A содержит ссылку на объект B, что позволяет им взаимодействовать друг с другом. Однако, если A имеет данные, к которым также имеют доступ дочерние элементы B, это создает ситуацию, когда данные могут быть изменены из нескольких источников. В результате информация, находящаяся в объекте B, утрачивает свою защиту, и происходит нарушение принципа инкапсуляции.
Несмотря на то что значительное количество разработчиков, работающих с объектно-ориентированным подходом, создают свои продукты подобной архитектуры, это уже нельзя назвать настоящим объектно-ориентированным программированием. Скорее, это становится просто беспорядком.
Риски, связанные с единственной парадигмой
Все упомянутые сложности, связанные с объектно-ориентированным программированием, имеют общую черту: использование наследования происходит даже в тех случаях, когда это не является оптимальным решением. При этом стоит отметить, что эти трудности не являются исключительно характерными для ОО-подхода, поскольку изначально он не подразумевал наследование. Гораздо большее значение здесь имеет страх отступить от установленной парадигмы и слепая привязанность к ней.
Тем не менее, чрезмерное увлечение одной парадигмой не ограничивается только объектно-ориентированным программированием. В контексте функционального программирования, например, возникают значительные трудности при работе с вводом данных и выводом информации на экран. Для решения таких задач более уместно использовать либо объектно-ориентированные, либо процедурные методы. Однако некоторые программисты предпочитают реализовывать ввод и вывод в виде чистых функций, в результате чего их код становится чрезмерно громоздким и трудным для понимания. В то же время, альтернативные подходы могли бы позволить решить эти задачи всего лишь с помощью нескольких ясных строк кода.
В рамках различных парадигм, подобно религиозным учениям, важна сбалансированность. Безусловно, такие фигуры, как Иисус, Мухаммед и Будда, принесли в мир выдающиеся идеи. Тем не менее, если строго придерживаться их наставлений до мельчайших деталей, это может существенно негативно сказаться на жизни как личной, так и окружающих. Важно соблюдать золотую середину.
Безусловно, функциональное программирование становится всё более популярным, в то время как объектно-ориентированное программирование в последние годы теряет свои позиции и подвергается значительной критике.
Безусловно, освоение новых парадигм является важным аспектом, однако их применение должно основываться на реальной необходимости. В конце концов, даже если объектно-ориентированное программирование воспринимается как универсальный инструмент, который разработчики используют для всех задач, это ли повод от него отказаться? Возможно, будет более разумно дополнить свой набор инструментов отвёрткой, ножом и ножницами, позволяя себе выбирать наиболее подходящий инструмент для решения конкретной проблемы.