что лучше ооп или функциональное программирование
Что выбрать: функциональное программирование или ООП?
Достоинства функционального программирования (FP) и объектно-ориентированного программирования (ООП) кажутся бесконечными спорами на любом техническом онлайн-форуме. Хотя сложно сказать однозначно, какой из них лучше, вы можете оценить их различия, чтобы увидеть, какой из них лучше.
Функциональное и объектно-ориентированное программирование: чем они отличаются?
Объектно-ориентированное программирование и функциональное программирование преследуют одну и ту же цель — разрабатывать гибкие программы, которые легко понять и не содержат ошибок. Но они основаны на разных подходах.
Объектно-ориентированного программирования
ООП — это парадигма, основанная на идее «объектов», содержащих данные в форме полей, часто называемых атрибутами; и код в форме процедур, часто называемых методами. Объектно-ориентированное программирование утверждает, что упрощает понимание того, как работает программа, объединяя данные и связанное с ними поведение в одном месте (называемом «объектом»).
Объектно-ориентированное программирование основывается на четырех ключевых принципах:
Объектно-ориентированное программирование эффективно, если у вас есть фиксированная серия операций над вещами, и по мере добавления новых вещей ваш код развивается. Однако вы не сможете легко определить, есть ли у объекта вызванная функция, если не отследите, произошло ли это с самого начала.
Функциональное программирование
FP — это процесс создания программного обеспечения путем составления чистых функций. Все объекты неизменяемы, а это означает, что однажды созданное не может быть изменено. Функциональное программирование утверждает, что данные и поведение — разные вещи и для ясности их следует разделять.
FP основывается на шести концепциях:
Ключевые отличия
Плюсы и минусы объектно-ориентированного программирования
Плюсы
ООП так популярно, потому что позволяет защитить вещи от нежелательного внешнего использования. Он скрывает переменные внутри класса и, таким образом, предотвращает доступ извне. Кроме того, ООП допускает модульность (возможность разделения функций программы на независимые модули) и управление общими состояниями.
Объекты можно легко повторно использовать в другом приложении. Легко создавать новые объекты для одного и того же класса, легко поддерживать и изменять код.
В ООП есть управление памятью. Это дает большое преимущество, когда дело доходит до создания больших программ, поскольку позволяет легко разделять вещи на более мелкие части и помогает различать компоненты, которые необходимо выполнить определенным образом.
Минусы
ООП нельзя использовать повторно. Поскольку некоторые функции зависят от класса, который их использует, их трудно использовать с другим классом. Кроме того, он менее эффективен, и с ним сложнее справиться. Многие объектно-ориентированные программы также предназначены для моделирования массивных архитектур и могут быть сложными.
Плюсы и минусы функционального программирования
Плюсы
FP предлагает такие преимущества, как ленивое вычисление, код без ошибок, вложенные функции, параллельное программирование. Он использует более декларативный подход, который концентрируется на том, что нужно сделать, и меньше на том, как это делать, с упором на эффективность и оптимизацию.
В функциональном программировании гораздо проще узнать, какие изменения были внесены, поскольку сам объект теперь является новым объектом с другим именем. Это эффективно, когда у вас есть фиксированный набор операций, и по мере развития вашего кода вы добавляете новые операции к существующим вещам.
Он хорошо работает, когда границы либо не требуются, либо уже предопределены. Это наиболее полезно в ситуациях, когда состояние не является фактором и переменные данные практически не задействованы.
В функциональном программировании проще моделировать реальные процессы, чем объекты. Его математическое происхождение делает его подходящим для случаев, требующих вычислений или всего, что включает преобразование и обработку. ООП в таких случаях будет неэффективным.
Минусы
FP — это манипулирование данными, и для написания кода требуется другое мышление. Хотя легко мыслить объектно-ориентированными терминами, преобразование реального сценария в простой потребует дополнительных умственных усилий.
Поскольку ФП труднее выучить, меньше людей программируют таким образом и, следовательно, естественно меньше информации по теме.
Какая из них лучше?
Очевидно, что разработчики ООП утверждают, что ООП — лучший подход к разработке программного обеспечения, в то время как команды FP утверждают, что FP лучше.
Хотя и объектно-ориентированное программирование, и функциональное программирование являются важными парадигмами. Преследующими одну и ту же цель разработки понятных программ без ошибок, их подходы различны. ООП следует модели императивного программирования, основанной на наборе примитивов, предоставляемых языком программирования. FP, с другой стороны, тесно связан с декларативным стилем, который подразумевает, что вы определяете только то, что необходимо выполнить, не указывая, как это сделать.
Похоже, что все согласны с тем, что ООП и ФП эффективны в любой конкретной ситуации, поэтому разработчики всегда должны выбирать парадигму программирования, которая делает процесс продуктивным и простым. Даже в этом случае мы не услышим в ближайшее время об окончании этих дебатов.
Когда применять функциональное программирование, а когда ООП — отвечают эксперты
Авторизуйтесь
Когда применять функциональное программирование, а когда ООП — отвечают эксперты
Многие слышали про функциональное программирование и, возможно, задавались вопросом: «А зачем оно, когда есть ООП?». Мы спросили у экспертов, когда стоит использовать ту или иную парадигму.
ведущий разработчик блока Инновационных решений группы компаний «Лига Цифровой Экономики»
Каждый инструмент хорош для определённого набора задач в определённой ситуации. Как некоторые могут забивать саморезы молотком, так и фанаты той или иной парадигмы при любых вводных могут задействовать то, чем лучше владеют. Однако опытные разработчики всё-таки руководствуются не симпатией к какой-либо технологии, а эффективностью. Также могут присутствовать ограничения, задаваемые заказчиком или программным окружением. Важен целый ряд факторов и характеристик разрабатываемого решения, его дальнейшей поддержки. Разумеется, выбор чаще опирается на собственный опыт, и в самом начале проектирования не всегда удаётся угадать будущее развитие и все нюансы использования ПО, так что универсального решения нет.
Объектно-ориентированное программирование (ООП) является более «традиционной» парадигмой. С её помощью разработано несчётное количество программ, в том числе огромные промышленные системы в финансовых организациях, телекоммуникации на производстве, складах, транспорте. Незнание принципов ООП фактически перекроет доступ ко всем этим системам.
Тут надо понимать, что если десяток лет пользовался ООП, то и сознание подстраивается под эту модель, проще проектировать именно через объекты и вызовы методов, а не через потоки данных и данные. Один из главных минусов ООП — чудовищная, запутанная система классов для системы, которая разрабатывается десятки лет большой командой. Как правило, некоторые «ядерные» классы оказываются на «дне» модели, их никто не рискует трогать даже в ущерб скорости разработки и устойчивости ПО. Появляются классы-наследники, переопределённые методы и прочий мусор, который со временем тоже становится «ядром системы».
Что касается функционального программирования (ФП). В каком-то сильно упрощённом виде оно используется и при ООП — в школах первые программы пишут функциями. С него нужно начинать изучение языков программирования, им же и завершать. Далее, в зависимости от нужд конкретного проекта, можно углублять знания той парадигмы, которая больше используется. Парадигма ФП влияет и на программирование, и на проектирование программного обеспечения. Для высоконагруженных систем переход к обработке потоков данных может быть спасением. Для выбора этой парадигмы в «большой» системе как минимум нужно иметь много данных и большую нагрузку (много вызовов, много пользователей). С одной стороны, она может дистанцировать бизнес-модель от реализации, с другой — позволит вовремя отвечать на запросы пользователей и иных внешних систем. Для небольших программ выбор ФП возможен, тут больше дело вкуса. Однако новичку может быть непросто разделить бизнес-модель на данные и потоки данных и спроектировать так, чтобы данные не хранились в классах и было чистое ФП.
Что выбрать новичку для изучения?
Изучить всё сразу не получится, но для быстрого старта карьеры, на мой взгляд, достаточно знать принципы ООП и иметь хотя бы общее представление о функциональных, процедурных языках: современные подходы используют некоторые более старые парадигмы, в новой реализации они могут быть очень эффективны. Если есть поверхностное знание о функциональном программировании — это вообще замечательно. Значит, у разработчика есть выбор, меньше ограничений на реализацию задуманного.
frontend-разработчик IT-компании MediaSoft
Я считаю, что каждый разработчик должен иметь представление и об ООП, и о ФП, знать сильные и слабые стороны каждого подхода и на основе этого определять, что лучше использовать для решения конкретной бизнес-задачи.
ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных (например содержат внутри себя ссылки на глобальные переменные) или коммуницируют с внешним миром (ввод-вывод).
В отличие от ООП, функциональное программирование характеризуется слабой связью функции с данными, которыми она оперирует. Это позволяет избежать побочных эффектов при выполнении функций — например чтения и изменения глобальных переменных, операций ввода-вывода и так далее. Детерминированные функции ФП возвращают один и тот же результат для одних и тех же аргументов.
Но эти подходы не являются взаимоисключающими. Нет необходимости выбирать только одну парадигму и следовать ей до конца. Вы можете передавать классы в чистые (то есть не связанные с внешними данными) функции или можете использовать чистые функции в качестве методов класса — одно другому не противоречит, а только дополняет. Если вы пишете простую и небольшую программу, следование той или иной парадигме — сугубо ваше личное мнение и видение прекрасного. Однако если вы пишете большой сервис с разноплановыми задачами, в определённый момент вы столкнетесь с необходимостью рефакторинга, так как для эффективного решения всех этих задач одного подхода будет недостаточно.
Приведу пример. Если вы пишете на Node.js, на первый взгляд удобнее использовать ФП. Дело в том, что сам по себе запрос на сервер — это функция с определённым входом и выходом (request, response). А работа с request’ом происходит с помощью цепочки функций (middleware), и результат (response) всегда будет одинаковый, если в качестве аргументов передавать одни и те же значения. Функциональный подход здесь смотрится естественно. С другой стороны, если Node.js-сервис подразумевает работу с БД, для описания моделей и работы с ними удобнее применить ООП-подход. Примером служит популярная библиотека sequelize.
На просторах frontend особой популярностью пользуются фреймворки, и каждый из них использует ту или иную парадигму, но для полноценной работы с ними необходимо знание как ООП, так и ФП. Взять для примера Angular: данный фреймворк построен на сервисах, которые в свою очередь являются классами, содержащими данные и методы для работы с ними. Однако при работе с библиотекой Redux, обычно работающей в паре с React, напрямую сталкиваешься с функциональным подходом, так как основная идея Redux — использование чистых функций без побочных эффектов.
ведущий iOS-разработчик в FINCH
ООП vs. ФП — вечная дилемма. Мне кажется, что наибольшей эффективности в разработке можно добиться, только если миксовать подходы. Точно не стоит писать проект только на ФП, потому что такой подход сильно ограничивает разработку: нужно постоянно прорабатывать поведение state.
Я бы порекомендовал начинающим разработчикам посмотреть отдельные, частные кейсы. Например, хороший кейс использования ФП, когда в проекте есть некая сортировка данных. Условно, есть датасет, в котором нужно отфильтровать данные по фамилии, а затем ещё и по имени. Функциональное программирование позволяет сделать это красиво и с умом.
Всегда стоит думать о логике проекта. Если в приложении много динамики, то архитектура REST API из функционального программирования отлично сработает, так делают те же ребята из tutu.ru. То же самое работает и наоборот — если у вас обычное сервисное приложение, где пользователю отображают JSON, то особо смысла использовать ФП нет.
старший инженер-программист практики Java компании «Рексофт»
«Когда применять функциональное программирование, а когда ООП?» — вопрос совсем непростой. Если посмотреть форумы, то понятно, что холивар возникает уже на этапе самого определения функционального программирования.
Если исходить из определения, что функциональный стиль — это когда результат выполнения кода всегда зависит только от поданных на вход значений, то лично я такой подход стараюсь применять как можно чаще. Это упрощает читабельность кода, его тестирование. Однако ФП более характеризуется тем, что аргументами одних функций являются другие, более простые функции, и вот это наиболее сложная часть, где можно легко выскочить за сложность вычислений O(n).
Не очень понятно, как сравнить ООП и функциональное программирование (ФП). Я считаю, что модели могут тесно пересекаться, не исключая друг друга, и где какую модель применять, зависит от архитектуры программы и задач, стоящими перед каждым модулем программы. Например ваш код имитирует движение транспортного средства (ТС). Нужно каждую секунду вычислять координаты ТС, его скорость, пройденный путь. Для того, чтобы это сделать, необходимо будет производить вычисления на основе предыдущих вычислений. Если применить функциональное программирование, то появляется необходимость хранения результатов вычислений и подачи их каждый раз на вход модели. Это создаст дополнительные логические сложности, поэтому в этой задаче лучше скомбинировать ООП и ФП. Для каждого ТС создаётся объект в стиле ООП, и каждый объект сам хранит свои предыдущие вычисления. Вам остаётся на вход подавать только ускорение, направление движения и время, в течение которого они действовали. Внутри же объекта ТС у вас будет два десятка методов, рассчитывающих его новое состояние. И вот тут рекомендация: стремиться большую часть из них сделать в виде простых функций, и только в одном или двух в вычисления добавить влияние его предыдущего состояния или используемого набора функций.
Сейчас порог входа в программисты очень высок, и изучать что-то одно и быть востребованным у программиста вряд ли получится.
Итак, какую парадигму выбрать?
ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных.
Функциональное программирование характеризуется слабой связью функции с данными, которыми она оперирует. Это позволяет избежать побочных эффектов при выполнении функций — например чтения и изменения глобальных переменных, операций ввода-вывода и так далее.
Тем не менее, эти подходы не являются взаимоисключающими. Вы можете передавать классы в чистые функции или использовать чистые функции в качестве методов класса. Нередко удачным подходом является именно смешение парадигм, а не использование какой-то одной.
При выборе парадигмы стоит смотреть на решаемую вами задачу, а также учитывать возможное развитие проекта, чтобы быть уверенным, что выбранная сегодня «правильная» парадигма не вынудит вас через полгода переписать весь проект.
Что до новичков, то определённо стоит изучить ООП, так как это более «традиционная» парадигма, которая много где используется. Но стоит иметь хотя бы общее представление о ФП, чтобы иметь у себя в арсенале ещё один инструмент.
Напоминаем, что вы можете задать свой вопрос экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков рубрики. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на experts@tproger.ru, мы расскажем, как это сделать.
Хинт для программистов: если зарегистрируетесь на соревнования Huawei Cup, то бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.
Перейти к регистрации
ФП vs ООП
Не так давно на хабре появилось несколько постов противопоставляющих функциональный и объектный подход, породивших в комментариях бурное обсуждение того, что вообще это такое — объектно ориентированное программирование и чем оно отличается от функционального. Я, пусть и с некоторым опозданием, хочу поделиться с окружающими тем, что думает по этому поводу Роберт Мартин, также известный, как Дядюшка Боб.
За последние несколько лет мне неоднократно доводилось программировать в паре с людьми, изучающими Функциональное Программирование, которые были предвзято настроены по отношению к ООП. Обычно это выражалось в формe утверждений типа: “Ну это слишком похоже на что-то объектное.”
Я думаю это происходит из убеждения, что ФП и ООП взаимно исключают друг друга. Многие похоже думают, что если программа функциональная, то она не объектно ориентированная. Полагаю, формирование такого мнения — логичное следствие изучения чего-то нового.
Когда мы берёмся за новую технику, мы часто начинаем избегать старых техник, которые использовали раньше. Это естественно, потому что мы верим, что новая техника “лучше” и следовательно старая техника наверное “хуже”.
В этом посте я обосную мнение, что хотя ООП и ФП ортогональны, это не взаимно исключающие понятия. Что хорошая функциональная программа может (и должна) быть объектно ориентированной. И что хорошая объектно ориентированная программа может (и должна) быть функциональной. Но для того, чтобы это сделать, нам придётся определиться с терминами.
Что такое ООП?
Я подойду к вопросу с редукционистских позиций. Есть много правильных определений ООП которые покрывают множество концепций, принципов, техник, паттернов и философий. Я намерен проигнорировать их и сосредоточиться на самой соли. Редукционизм тут нужен из-за того, что всё это богатство возможностей, окружающее ООП на самом деле не является чем-то специфичным для ООП; это просто часть богатства возможностей встречающихся в разработке программного обеспечения в целом. Тут я сосредоточусь на части ООП, которая является определяющей и неустранимой.
Посмотрите на два выражения:
Никакой семантической разницы явно нет. Вся разница целиком и полностью в синтаксисе. Но одно выглядит процедурным, а другое объектно ориентированным. Это потому что мы привыкли к тому, что для выражения 2. неявно подразумевается особая семантика поведения, которой нет у выражения 1. Эта особая семантика поведения — полиморфизм.
Когда мы видим выражение 1. мы видим функцию f, которая вызывается в которую передаётся объект o. При этом подразумевается, что есть только одна функция с именем f, и не факт, что она является членом стандартной когорты функций, окружающих o.
С другой стороны, когда мы видим выражение 2. мы видим объект с именем o которому посылают сообщение с именем f. Мы ожидаем, что могут быть другие виды объектов, котоые принимают сообщение f и поэтому мы не знаем, какого конкретно поведения ожидать от f после вызова. Поведение зависит от типа o. то есть f — полиморфен.
Вот этот факт, что мы ожидаем от методов полиморфного поведения — суть объектно ориентированного программирования. Это редукционистское определение и это свойство неустранимо из ООП. ООП без полиморфизма это не ООП. Все другие свойства ООП, такие как инкапсуляция данных и методы привязанные к этим данным и даже наследование имеют больше отношения к выражению 1. чем к выражению 2.
Программисты, использующие Си и Паскаль (и до некоторой степени даже Фортран и Кобол) всегда создавали системы инкапсулированных функций и структур. Чтобы создать такие структуры даже не нужен объектно ориентированный язык программирования. Инкапсуляция и даже простое наследование в таких языках очевидны и естественны. (В Си и Паскале более естественно, чем в других)
Поэтому то, что действительно отличает ООП программы от не ООП программ это полиморфизм.
Возможно вы захотите возразить, что полифорфизм можно сделать просто используя внутри f switch или длинные цепочки if/else. Это правда, поэтому мне нужно задать для ООП ещё одно ограничение.
Использование полиморфизма не должно создавать зависимости вызывающего от вызываемого.
Чтобы это объяснить, давайте ещё раз посмотрим на выражения. Выражение 1: f(o), похоже зависит от функции f на уровне исходного кода. Мы делаем такой вывод потому что мы также предполагаем, что f только одна и что поэтому вызывающий должен знать о вызываемом.
Однако, когда мы смотрим на Выражение 2. o.f() мы предполагаем что-то другое. Мы знаем, что может быть много реализаций f и мы не знаем какая из этих функций f будет вызвана на самом деле. Следовательно исходный код, содержащий выражение 2 не зависит от вызываемой функции на уровне исходного кода.
Если конкретнее, то это означает, что модули (файлы с исходным кодом), которые содержат полиморфные вызовы функций не должны ссылаться на модули (файлы с исходным кодом), которые содержат реализацию этих функций. Не может быть никаких include или use или require или каких-то других ключевых слов, которые создают зависимость одних файлов с исходным кодом от других.
Итак, наше редукционистское определение ООП это:
Техника использующая динамический полиморфизм чтобы вызывать функции и не создающая зависимостей вызвающего от вызываемого на уровне исходного кода.
Что такое ФП?
И опять я буду использовать редукционистский подход. У ФП есть богатые традиции и история, корни которых глубже, чем само программирование. Существуют принципы, техники, теоремы, философии и концепции, которыми пронизана эта парадигма. Я всё это проигнорирую и перейду сразу к самой сути, к неотъемлемому свойству которое отделяет ФП от других стилей. Вот оно:
В функциональной программе вызов функции с тем же аргументом даёт тот же результат независимо от того, как долго работала программа. Это иногда называют референциальной прозрачностью.
Из сказанного выше вытекает следствие, что f не должна менять части глобального состояния, которые влияют на поведение f. Более того, если мы скажем, что f представляет все функции в системе — то есть все функции в системе должны быть референциально прозрачными — тогда ни одна функция в системе не может изменить глобальное состояние. Ни одна функция не может сделать ничего, что может привести к тому, что другая функция из системы вернёт другое значение при тех же аргументах.
У этого есть и более глубокое следствие — ни одно именованное значение нельзя менять. То есть оператора присваивания нет.
Если тщательно обдумать это утверждение, то можно прийти к заключению, что программа, состоящая только из референциально прозрачных функций ничего не может сделать — так как любое полезное поведение системы меняет состояние чего-нибудь; даже если это просто состояние принтера или дисплея. Однако, если из требований к референциальной прозрачности исключить железо и все элементы окружающего мира оказывается, что мы можем создавать очень полезные системы.
Фокус, конечно, в рекурсии. Рассмотрим функцию которая принимает структуру с состоянием в качестве аргумента. Этот аргумент состоит из всей информации о состоянии, которая нужна функции для работы. Когда работа окончена, функция создаёт новую структуру с состоянием, содержимое которой отличается от предыдущей. И последним действием функция вызывает саму себя с новой структурой в качестве аргумента.
Это только один из простых трюков, которые фукциональная программа может использовать чтобы хранить изменения состояния без необходимости изменять состояние [1].
Итак, редукционистское определение функционального программирования:
Референциальная Прозрачность — переприсваивать значения нельзя.
ФП против ООП
К этому моменту и сторонники ООП и сторонники ФП уже смотрят на меня через оптические прицелы. Редукционизм не лучший способ завести друзей. Но иногда он полезен. В данном случае, я думаю что полезно пролить свет на никак не утихающий холивар ФП против ООП.
Ясно, что два редукционистских определения, которые я выбрал, совершенно ортогональны. Полиморфизм и Референциальная Прозрачность не имеют никакого отношения друг к другу. Они никак не пересекаются.
Но ортогональность не подразумевает взаимного исключения (спросите Джеймса Клерка Максвелла). Вполне можно создать систему, которая использует и динамический полиморфизм и референциальную прозрачность. Это не только возможно, это правильно и хорошо!
Почему эта комбинация хороша? По точно тем же причинам, что оба её компонента! Системы построенные на динамическом полиморфизме хороши, потому что они обладают низкой связностью. Зависимости можно инвертировать и расположить по разные стороны архитектурных границ. Эти системы можно тестировать используя Моки и Фейки и другие виды Тестовых Дублей. Модули можно модифицировать не внося изменения в другие модули. Поэтому такие системы проще изменять и улучшать.
Системы, построенные на референциальной прозрачности тоже хороши, потому что они предсказуемы. Неизменяемость состояния делает такие системы проще для понимания, изменения и улучшения. Это значительно уменьшает вероятность возникновения гонок и других проблем, связанных с многопоточностью.
Главная мысль тут такая:
Нет никакого холивара ФП против ООП
ФП и ООП хорошо работают вместе. И то и другое хорошо и правильно использовать в современных системах. Система, которая построена на комбинации принципов ООП и ФП максимизирует гибкость, поддерживаемость, тестируемость, простоту и прочность. Если убрать одно ради добавления другого это только ухудшит структуру системы.
[1] Так как мы используем машины с архитектурой Фон Неймана мы предполагаем, что в них есть ячейки памяти, состояние которых на самом деле изменяется. В механизме рекурсии, который я описал, оптимизация хвостовой рекурсии не даст создавать новые стекфреймы и будет использоваться первоначальный стекфрейм. Но это нарушение референциальной прозрачности (обычно) скрыто от программиста и ни на что не влияет.