Актуально ли сегодня ООП?

в 13:00, , рубрики: ruvds_переводы, инкапсуляция, микросервисы, ооп, распределенные системы, управление состоянием

Актуально ли сегодня ООП? - 1


Почти каждый день возникают дискуссии с критикой или восхвалением объектно-ориентированного программирования. «Java устарела!», «Java потрясающая!». В этой статье я проведу прагматичное исследование ООП на 2024 год.

Термин объектно-ориентированное программирование придумал Алан Кэй. Кэй был членом команды PARC, которая изобрела графический интерфейс пользователя, сделавший таким полезным современный Интернет, персональные компьютеры, планшеты и смартфоны. Ещё она изобрела некоторые из объектно-ориентированных языков, на которых мы сегодня реализуем эти GUI.

Если отсечь все эмоции, связанные с ООП, то что останется? По-прежнему ли ООП является эффективным инструментом разработки ПО, или оно превратилось в устаревшее увлечение? Профессионалам важно знать ответ на этот вопрос!

▍ Что Алан Кэй сказал про ООП

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

Давайте разобьём это на составляющие:

  1. «Только обмен сообщениями» означает, что объекты общаются друг с другом при помощи сообщений. Сообщение объекту — это запрос на выполнение процедуры, то есть оно приведёт к изменению в состоянии одного или нескольких объектов или к возврату значения. Это подчёркивает, что объекты должны взаимодействовать через чётко определённые интерфейсы, используя сообщения для запросов действий.
  2. «Локальное удержание и защита, сокрытие состояния» означает инкапсуляцию и сокрытие информации. Инкапсуляция — это привязка данных к методам, работающим с этими данными, или ограничение прямого доступа к некоторым компонентам объекта. Сокрытие информации — это принцип сокрытия от пользователей подробностей реализации класса. Объект раскрывает только те операции, которые безопасны и релевантны для пользователя. Это защищает внутреннее состояние объекта и гарантирует, что объект можно будет использовать без необходимости понимания таящейся внутри него сложности.
  3. «Крайне позднее связывание» — под поздним связыванием подразумевается то, что конкретный вызываемый код определяется во время исполнения, а не во время сборки. Это позволяет обеспечивать более гибкое и динамическое поведение кода, при котором объекты различных типов можно использовать взаимозаменяемо при условии, если они предоставляют одинаковый внешний интерфейс, даже если внутри них действия реализуются по-разному. Позднее связывание описывает систему, максимизирующую эту гибкость, позволяя создавать очень динамическую и адаптивную модель компонентов.

В описании Кэя упор делается на взаимодействие автономных компонентов через чётко определённые интерфейсы с сохранением приватности их внутренних процессов и данных; при этом обеспечивается высокая степень гибкости и динамичности в том, когда и как компоненты могут взаимодействовать друг с другом. Эти принципы могут сделать ПО более модульным, более простым в поддержке и более гибким.

▍ Чего он не сказал

Кэй не упомянул наследование — концепцию, с которой возникали проблемы у многих ООП-программистов. В его заявлении чётко говорится, что он не считает наследование обязательным требованием для объектно-ориентированного программирования.
Хотя создание производных классов повышает степень повторного использования кода и полиморфизм, оно имеет и некоторые недостатки:

  1. Производные классы (подклассы) сильно связаны со своими родительскими классами.
  2. Иерархии наследования могут усложнить понимание и трассировку кода.
  3. Изменения в базовом классе могут легко поломать его подклассы.
  4. Переопределение методов в подклассах может привести к непониманию того, какой экземпляр метода вызывается.
  5. Подклассы часто полагаются на знания о подробностях реализации родительских классов, что ломает инкапсуляцию.
  6. Изменение вышестоящего класса может потребовать обширных изменений во многих его подклассах.
  7. Подклассы добавляют дополнительные состояния и поведения, способные усложнить тестирование.

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

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

▍ Что же означает ООП для нас сегодня?

При проектировании, разработке, развёртывании, эксплуатации и поддержке ПО сложность оказывается основным фактором, влияющим на затраты.

ПО само по себе сложно. Чем больше становится система, чем сильнее она меняется, тем сложнее обычно становится.

Актуально ли сегодня ООП? - 2

Рисунок 1: Зависимость сложности от количества компонентов. Сложность = n(n-1)/2

Как показано на Рисунке 1, сложность приложений существенно возрастает с увеличением количества составляющих приложение элементов и связей между ними.

Один из самых эффективных способов управления сложностью ПО — это применение моделей компонентов, которые:

  1. Упрощают понимание и изменение отдельных компонентов ПО.
  2. Изолируют компоненты ПО от изменений в других компонентах.
  3. Минимизируют потенциальные помехи командам, работающим над разными частями системы.
  4. Упрощают выпуск новых и обновлённых компонентов ПО.

Алан Кэй и его команда в PARC выбрали ООП для разработки GUI по тем же самым причинам, по которым выбор ООП сегодня логичен для разработки как конкурентных, так и распределённых приложений. Компонуемые микросервисы, соответствующие определению ООП Кэя, доказывают ценность его идей.

▍ Почему мысли Кэя об ООП важны?

Фундаментальная проблема заключается в том, что разработка ПО — это сложный процесс.

Отзывчивое сетевое распределённое ПО, удобное в создании и поддержке, надёжное в работе, может быть очень сложной системой.

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

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

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

▍ «Только обмен сообщениями»

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

  1. Обмен сообщениями позволяет обеспечить слабое связывание между разными частями системы. Так как компоненты общаются посредством сообщений и не требуют знания внутреннего устройства друг друга, изменения в одном компоненте не влияют напрямую на другие. Это упрощает обновления и поддержку.
  2. Системы, спроектированные на принципе обмена сообщениями, проще масштабировать. Компоненты можно распределить по множеству серверов или процессов, а поскольку они общаются друг с другом через сообщения, система может справляться с повышенными нагрузками, добавляя больше ресурсов, без необходимости внесения существенных изменений в архитектуру.
  3. Обмен сообщениями сам по себе поддерживает конкурентность. Разные части системы могут обрабатывать сообщения одновременно, пользуясь преимуществами многоядерных процессоров и распределённых вычислительных ресурсов для повышения производительности.
  4. Так как компоненты общаются посредством чётко определённых сообщений, их можно реализовывать на разных языках программирования и с помощью разных технологий. Это позволяет разработчикам выбирать наиболее подходящие под требования каждого компонента инструменты.
  5. Системы обмена сообщениями можно спроектировать так, чтобы они были устойчивы к сбоям. В случае сбоя компонента сообщения можно попробовать отправить повторно или перенаправить другому экземпляру компонента, обеспечив доступность системы. Так как компоненты разделены, сбой в одной области с меньшей вероятностью приведёт к аварии всей системы.
  6. Обмен сообщениями упрощает интеграцию разделённых систем. Разные системы могут общаться при помощи обмена сообщениями, даже если они созданы на основе разных технологий и работают на разных платформах.
  7. Обмен сообщений естественным образом поддерживает и синхронные, и асинхронные операции: отправитель может передать сообщение и ждать ответа или же опубликовать событие и не ждать. Это может привести к более эффективному использованию ресурсов и повысить общую отзывчивость системы.
  8. В системе на основе обмена сообщений все данные в сообщениях передаются по значению, а не по ссылке, что гарантирует потокобезопасность и неизменяемость сообщений, а также невозможность создания ими побочных эффектов.
  9. В системе на основе обмена сообщениями перемещающиеся между компонентами сообщения можно логировать и отслеживать, обеспечивая наблюдаемость поведения и производительности системы. Это может быть очень ценно для отладки, настройки производительности и понимания взаимодействий в системе.
  10. Системы обмена сообщениями поддерживают транзакционный обмен сообщениями, что позволяет выполнять сложные операции с задействованием нескольких этапов, которые можно рассматривать как единую атомарную транзакцию. Это гарантирует согласованность и надёжность данных даже в случае частичных сбоев.
  11. Общающиеся через сообщения компоненты часто можно тестировать изолированно, симулируя входящие сообщения и наблюдая за ответами. Это упрощает создание интеграционных и юнит-тестов.

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

Актуально ли сегодня ООП? - 3

Рисунок 2: Стратегии обмена сообщениями

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

Актуально ли сегодня ООП? - 4

Рисунок 3: Оркестрация обмена сообщениями. Под процессом здесь подразумевается активный экземпляр виртуальной машины Java

В показанном выше примере сообщения M2-M4 исходят от Component 1 и доставляются целевым компонентам (2, 3 и 4). Карты адресов компонентов автоматически распространяются между оркестраторами.

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

Актуально ли сегодня ООП? - 5

Рисунок 4: Маршрутизация сообщений

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

▍ «Локальное удержание и защита, сокрытие состояния»

Эти принципы являются фундаментом для создания надёжного, удобного в поддержке и безопасного ПО. В разработке ПО эти принципы имеют следующие преимущества:

  1. Сокрытие внутреннего состояния объекта от внешнего мира, обеспечение доступа только через чётко определённый интерфейс. Это скрывает сложность управления состоянием и защищает целостность объекта, предотвращая перевод объекта в несогласованное состояние внешними сущностями.
  2. Локализация состояния и логики управления им внутри компонента, упрощение понимания и поддержки системы. Можно сделать так, чтобы изменения в управлении состоянием компонента минимально влияли на другие части системы.
  3. Повышает модульность благодаря тому, что позволяет разработчикам проектировать системы, в которых компоненты автономны и имеют чёткие интерфейсы для взаимодействия. Такая модульность помогает в многократном использовании компонентов в разных частях системы или в разных проектах.
  4. Защита состояния объектов от неавторизованного доступа — ключевой аспект целостности ПО. Скрывая состояние и раскрывая только управляемый интерфейс, система может обеспечить выполнение только авторизованных действий, снижая риск уязвимостей целостности.
  5. Инкапсуляция состояния и логики манипулирования им упрощает тестирование и отладку компонентов. Так как управление состоянием выполняется внутри компонента, разработчики могут сосредоточиться на тестировании поведения компонента в изоляции, прежде чем интегрировать его в систему.
  6. При управлении состоянием и поведением, а также при их инкапсуляции систему легче масштабировать или совершенствуя отдельные компоненты, или добавляя новые, соответствующие уже имеющимся интерфейсам. Такие локализация и управление состоянием обеспечивают возможность и вертикальной, и горизонтальной стратегий масштабирования.
  7. Защита внутреннего состояния компонентов и обеспечение управления всеми переходами между состояниями через чётко определённый интерфейс, благодаря чему система более устойчива к ошибкам и непреднамеренным побочным эффектам. Это повышает надёжность приложений.
  8. Обеспечение разбиения системы на отдельные фичи, функциональность которых пересекается минимально. Такое разбиение упрощает управление сложностью, так как разработчики могут единовременно заниматься только одним аспектом системы.

▍ «Крайне позднее связывание»

Под этим подразумевается откладывание решения об исполнении конкретного кода на максимально долгий срок, обычно на этап выполнения программы, а не её сборки. Прогресс в программировании позволяет нам отодвигать связывание модулей на всё более дальний момент:

  1. Компонуемые компоненты, динамически загружаемые в среде исполнения для расширения функциональности приложения. Это вид компоновки, позволяющий приложениям иметь высокую степень расширяемости и настраиваемости.
  2. Языки, использующие JIT-компиляцию, например, Java и C#, компилируют код в среде исполнения, а языки наподобие TypeScript являются интерпретируемыми. Это позволяет создавать динамическую компоновку, при которой компилятор/интерпретатор и компоновщик могут оптимизировать исполняемый файл под конкретное оборудование, на котором он выполняется.
  3. При работе с микросервисами компоновка принимает новые формы. Сервисы общаются друг с другом по сети при помощи легковесных протоколов, по сути, компонуя распределённые компоненты во время исполнения.

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

У применения крайне позднего связывания в разработке ПО есть множество преимуществ:

  1. Позднее связывание повышает гибкость ПО благодаря простоте изменения способов взаимодействия частей системы без перекомпиляции, а иногда и без перезапуска системы. Такая адаптируемость критически важна в средах с часто меняющимися требованиями и необходимостью высокого уровня доступности систем.
  2. Системы, спроектированные на основе крайне позднего связывания, могут изменять своё поведение во время исполнения в зависимости от ввода пользователя, конфигурации или внешних данных. Это свойство позволяет приложениям обеспечивать высокодинамические фичи с возможностью добавления, удаления и обновления компонентов на лету.
  3. Позднее связывание упрощает интеграцию с другими системами или компонентами, так как специфика этих интеграций может быть определена во время исполнения. Это особенно полезно в ситуациях с задействованием сторонних API или сервисов, когда подробности могут быть неизвестны до времени исполнения.
  4. Разделяя компоненты и откладывая решения об их взаимодействиях на время исполнения, системы могут достигать более высоких уровней модульности и повторного использования. Компоненты, спроектированные для взаимодействия через интерфейсы с поздним связыванием, можно легко использовать повторно в других контекстах или приложениях.
  5. Позднее связывание обеспечивает поддержку быстрого прототипирования и итеративной разработки, позволяя разработчикам вносить изменения и видеть их влияние мгновенно, без длительной компиляции. Это способно существенно ускорить процесс разработки и упростить эксперименты.
  6. Программные системы, проектируемые с расчётом на позднее связывание, может быть проще поддерживать и они могут эволюционировать со временем. Так как связи между компонентами определяются во время исполнения, обновление или замену компонентов можно выполнять с минимальным влиянием на остальную часть системы.
  7. Позднее связывание обеспечивает более высокие степени настраиваемости и расширяемости, так как добавление новых поведений или обновление имеющихся поведений может выполняться во время исполнения.

▍ Подведём итог

По-прежнему ли ООП остаётся эффективным инструментом для разработки ПО или это просто устаревшее поветрие в мире программирования? Ответ таков: ООП не устарело. На самом деле, ООП стало ещё более важным в современном мире распределённых вычислений, в котором критически важны эффективные модели компонентов и коммуникаций.

Создание отзывчивого сетевого распределённого ПО, экономически выгодного в разработке и поддержке, работающего эффективно и надёжно, может быть сложной задачей. Алан Кэй многократно доказал, что использование ООП в том виде, каким оно задумывалось командой PARC, может помочь вам.

▍ Рекомендуемые материалы

  • Computer Software, Алан Кэй, Scientific American за сентябрь 1984 года
  • Component Software — Beyond Object-Oriented Software Клеменса Шиперски
  • В статье Composable Services рассказывается о том, что делает сервис компонуемым.
  • Designing with Composable Services — использование компонуемых сервисов для оптимизации компромиссов при проектировании приложений.
  • Building with Composable Services — более формальное и подробное описание паттерна проектирования компонуемых сервисов.
  • The Magic of Message Orchestration — знакомство со связями, объединяющими компонуемые компоненты.
  • Building Software Systems — о борьбе со сложностью разработки ПО.
  • Designing a REST API — о том, почему обмен сообщениями с использованием состояния лучше, чем удалённые вызовы процедур.

Автор:
ru_vds

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js