Мой перевод, как и оригинальный доклад вызвали неоднозначную реакцию в комментариях. Поэтому я решил перевести статью-ответ дяди Боба на оригинальный материал.Множество программистов на протяжении последних лет утверждают, что ООП и ФП — являются взаимоисключающими. С высоты башни из слоновой кости в облаках, ФП-небожители иногда поглядывают вниз на бедных наивных ООП-программистов и снисходят до надменных комментариев. Приверженцы ООП в свою очередь косо смотрят на «функционыльщиков», не понимая, зачем чесать левое ухо правой пяткой.
Эти точки зрения игнорируют саму суть ООП и ФП парадигм. Вставлю свои пять копеек.
ООП не про внутреннее состояние
Объекты (классы) – не структуры данных. Объекты могут использовать структуры данных, но их детали реализации скрыты. Вот почему существуют приватные члены классов. Извне вам доступны только методы (функции), поэтому объекты про поведение, а не состояние.
Использование объектов в качестве структур данных – признак плохого проектирования. Инструменты, вроде Hibernate называют себя ORM. Это некорректно. ORM не отображают реляционные данные на объекты. Они отображают реляционные данные на структуры данных. Эти структуры – не объекты. Объекты группируют поведение, а не данные.
Думаю, здесь дядя Боб ругает ORM за то они часто подталкивают к анемичной модели, а не к богатой.Функциональные программы, как и объектно-ориентированные являются композицией функций преобразования данных. В ООП принято объединять данные и поведение. И что? Это действительно так важно? Есть огромная разница между
f(o), o.f()
и (f o)
? Что, вся разница в синтаксисе. Так в чем же настоящие различия между ООП и ФП? Что есть в ООП, чего нет в ФП и наоборот?ФП навязывает дисциплину в присвоение (immutability)
В «тру фп» нет оператора присвоения. Термин «переменная» вообще не применим к функциональным ЯП, потому что однажды присвоив значение его нельзя изменить.
Да. Да. Апологеты ФП часто указывают на то что функции – объекты первого класса. В Smalltalk функции – тоже объекты первого класса. Smaltalk – объектно-ориентированный, а не функциональный язык.
Ключевое отличие не в этом, а в отсутствии удобного оператора присваивания. Значит ли это, в ФП вообще нет изменяемого состояния? Нет. В ФП языках есть всевозможные уловки, позволяющие работать с изменяемым состоянием. Однако, чтобы сделать это, вам придется совершить определенную церемонию. Изменение состояния выглядит сложным, громоздким и чужеродным в ФП. Это исключительная мера, к которой прибегают лишь изредка и неохотно.
ООП навязывает дисциплину в работе с указателями на функции
ООП предлагает полиморфизм в качестве замены указателей на функции. На низком уровне полиморфизм реализуется с помощью указателей. Объектно-ориентированные языки просто делают эту работу за вас. И это здорово, потому что работать с указателями на функции напрямую (как в C) неудобно: всей команде необходимо придерживаться сложных и неудобных соглашений и следовать им в каждом случае. Обычно, это просто не реалистично.
В Java все функции виртуальные. Это значит, что все функции в Java вызываются не напрямую, а с помощью указателей на функции.
Если вы хотите использовать полиморфизм в C вам придется работать с указателями вручную и это сложно. Хотите полиморфизм в Lisp: придется передавать функции в качестве аргументов самостоятельно (кстати, это называется паттерном стратегия). Но в объектно-ориентированных языках все это есть из коробки: бери и пользуйся.
Взаимоисключающие?
Являются две эти дисциплины взаимоисключающими? Может ли ЯП навязывать дисциплину в присваивании и при работе с указателями на функции. Конечно может! Эти вещи вообще не связаны. Эти парадигмы – не взаимоисключающие. Это значит, что можно писать объектно-ориентированные функциональные программы.
Это также значит, что принципы и паттерны ООП могут использоваться и в функциональных программах, если вы принимаете дисциплину «указателей на функции». Но зачем это «функциональщикам»? Какие новые преимущества это им даст? И что могут получить объектно-ориентированные программы от неизменяемости.
Преимущества полиморфизма
У полиморфизма всего одно преимущество, но оно значительно. Это инверсия исходного кода и рантайм-зависимостей.
В болшинстве систем когда одна функция вызывает другую, рантайм-зависимости и зависимости на уровне исходного кода однонаправленны. Вызывающий модуль зависит от вызываемого модуля. Но в случае полиморфизма вызывающий модуль все еще зависит от вызываемого в рантайме, но исходный код вызываемого модуля не зависит от исходного кода вызываемого модуля. Вместо этого оба модуля зависят от полиморфного интерфейса.
Эта инверсия позволяет вызываемого модулю вести себя как плагину. Действительно, плагины так и работают. Архитектура плагинов крайне надежна, потому что стабильные и важные бизнес-правила могут храниться отдельно от подверженных изменениям и не столь важных правил.
Таким образом, для надежности системы должны применять полиморфизм, чтобы создать значимые архитектурные границы.
Преимущества неизменяемости
Преимущества неизменяемых данных очевидны – вы не столкнетесь с проблемами одновременных обновлений, если вы никогда ничего не обновляете.
Так как большинство функциональных ЯП не предлагает удобного оператора присвоения, в таких программах нет значительных изменений внутреннего состояния. Мутации зарезервированы для специфических ситуаций. Секции, содержащие прямое изменение состояния, могут быть отделены от многопоточного доступа.
Итого, функциональные программы гораздо безопаснее в многопоточной и многопроцессорной средах.
Занудные философствования
Конечно приверженцы ООП и ФП будут против моего редукционистского анализа. Они будут настаивать на том, что существуют значительные философские, филологические и математические причины, почему их любимый стиль лучше другого. Моя реакция следующая: Пфффф! Все думают, что их подход лучше. И все ошибаются.
Так что там про принципы и паттерны?
Что вызвало у меня такое раздражение? Первые слайды намекают на то что все принципы и паттерны, разработанные нами за десятилетия работы применимы только для ООП. А в ФП все решается просто функциями.
Вау, и после этого вы что-то говорите про редукционизм? Идея проста. Принципы остаются неизменными, независимо от стиля программирования. Факт, что вы выбрали ЯП без удобного оператора присвоения, не значит, что вы можете игнорировать SRP или OCP, что эти принципы будут каким-то образом работать автоматически. Если паттерн «Стратегия» использует полиморфизм, это еще не значит, что он не может применяться в функциональном ЯП (например Clojure).
Итого, ООП работает, если вы знаете, как его готовить. Аналогично для ФП. Функциональные объектно-ориентированные программы – вообще отлично, если вы действительно понимаете, что это значит.