"Многие функции, действующие на основе нескольких абстракций" против OOP
Создатель Clojure language утверждает, что "открытый и большой набор функций работает с открытым и небольшим набором расширяемых абстракций является ключом к алгоритмическому повторному использованию и библиотечной совместимости". Очевидно, это противоречит типичному подходу ООП, где вы создаете множество абстракций (классов) и относительно небольшой набор функций, работающих на них. Пожалуйста, предложите книгу, главу в книге, статью или ваш личный опыт, который подробно описывает темы:
- мотивируя примеры проблем, которые появляются в ООП, и как использование "многих функций при нескольких абстракциях" будет адресовано им.
- Как эффективно создавать дизайн MFUFA
- как реорганизовать OOP-код в сторону MFUFA
- как синтаксис языков ООП мешает MFUFA
* MFUFA: "многие функции при нескольких абстракциях"
Ответы
Ответ 1
В программировании есть два основных понятия "абстракция":
- параметризация ( "полиморфизм", общая).
- инкапсуляция (скрытие данных),
[Edit: Эти два являются дуальными. Первая - это абстракция на стороне клиента, вторая абстракция на стороне исполнителя (и в случае, если вы заботитесь об этих вещах: в терминах формальной логики или теории типов они соответствуют универсальной и экзистенциальной квантификации, соответственно).
В OO класс представляет собой функцию кухонной раковины для достижения обоих видов абстракции.
Ad (1), для почти каждого "шаблона" вам нужно определить собственный класс (или несколько). С другой стороны, в функциональном программировании у вас часто есть более легкие и прямые методы для достижения тех же целей, в частности, функций и кортежей. Часто указывается, что большинство "шаблонов проектирования" из GoF избыточны в FP, например.
Ad (2), инкапсуляция необходима немного реже, если у вас нет изменчивого состояния, задерживающего всюду, что вам нужно держать под контролем. Вы все еще строите ADT в FP, но они, как правило, более простые и более общие, и, следовательно, вам нужно меньше их.
Ответ 2
Когда вы пишете программу в объектно-ориентированном стиле, вы делаете акцент на выражении области домена с точки зрения типов данных. И на первый взгляд это выглядит неплохо - если мы работаем с пользователями, почему бы не иметь класс User
? И если пользователи продают и покупают автомобили, почему бы не иметь класс Car
? Таким образом, мы можем легко поддерживать поток данных и контроля - он просто отражает порядок событий в реальном мире. Хотя это довольно удобно для объектов домена, для многих внутренних объектов (т.е. Объектов, которые не отражают ничего из реального мира, но встречаются только в программной логике), это не так хорошо. Возможно, лучшим примером является ряд типов коллекций в Java. В Java (и многих других языках ООП) есть как массивы, так и List
s. В JDBC есть ResultSet
, который также является видом коллекции, но не реализует интерфейс Collection
. Для ввода вы часто используете InputStream
, который предоставляет интерфейс для последовательного доступа к данным - точно так же, как связанный список! Однако он не реализует какой-либо интерфейс сбора данных. Таким образом, если ваш код работает с базой данных и использует ResultSet
, будет сложнее реорганизовать его для текстовых файлов и InputStream
.
Принцип MFUFA учит нас уделять меньше внимания определению типа и более общим абстракциям. По этой причине Clojure вводит единую абстракцию для всех перечисленных типов - последовательность. Любой итерабельный автоматически принуждается к последовательности, потоки - это только ленивые списки, и набор результатов может быть легко преобразован в один из предыдущих типов.
Другим примером является использование интерфейса PersistentMap
для структур и записей. С такими общими интерфейсами становится очень легко создавать нерегулярные подпрограммы и не тратить много времени на рефакторинг.
Подводя итоги и ответим на ваши вопросы:
- Один простой пример проблемы, которая появляется в ООП, часто: чтение данных из разных источников (например, БД, файл, сеть и т.д.) и обработка его таким же образом.
- Чтобы сделать хороший дизайн MFUFA, попробуйте сделать абстракции максимально распространенными и избегать специальных внедрений. Например. избегать типов a-la
UserList
- List<User>
достаточно хорош в большинстве случаев.
- Следуйте рекомендациям из пункта 2. Кроме того, попробуйте добавить столько интерфейсов к вашим типам данных (классам), насколько это возможно. Например, если вам действительно нужно иметь
UserList
(например, когда он должен иметь много дополнительных функций), добавьте в его определение оба интерфейса List
и Iterable
.
- OOP (по крайней мере, в Java и С#) не очень хорошо подходит для этого принципа, потому что они пытаются инкапсулировать все поведение объекта во время первоначального проектирования, поэтому становится сложнее добавлять к ним больше функций. В большинстве случаев вы можете расширить класс и поставить методы, необходимые в новый объект, но 1) если кто-то другой реализует свой собственный производный класс, он не будет совместим с вашим; 2) иногда классы
final
или все поля сделаны private
, поэтому производные классы не имеют к ним доступа (например, для добавления новых функций в класс String
следует реализовать дополнительный класс StringUtils
). Тем не менее, правила, описанные выше, значительно облегчают использование MFUFA в OOP-коде. И лучшим примером здесь является Clojure, который изящно реализован в стиле OO, но по-прежнему следует принципу MFUFA.
UPD. Я помню еще одно описание различий между объектно-ориентированными и функциональными стилями, которые, возможно, суммируют все, что я сказал выше: разработка программы в стиле OO - это мышление с точки зрения типов данных (существительные), при проектировании в функциональном стиле мышление с точки зрения операций (глаголы). Вы можете забыть, что некоторые существительные похожи (например, забывают о наследовании), но вы всегда должны помнить, что многие глаголы на практике делают одно и то же (например, имеют одинаковые или подобные интерфейсы).
Ответ 3
Более ранняя версия цитаты:
"Простая структура и естественная применимость списков отражаются в функциях, которые удивительно неиосинхроничны. В Паскале множество декларируемых структур данных индуцирует специализацию внутри функций, которые препятствуют и наказывают случайное сотрудничество. Лучше иметь 100 функций для работы одна структура данных, чем 10 функций, функционирует на 10 структурах данных".
... происходит из предисловия к знаменитой книге SICP. Я считаю, что в этой книге есть много подходящего материала по этой теме.
Ответ 4
Я думаю, вы не понимаете, что разница между библиотеками и программами.
Библиотеки OO, которые хорошо работают, обычно генерируют небольшое количество абстракций, которые используются программами для создания абстракций для своего домена. Большие библиотеки OO (и программы) используют наследование для создания различных версий методов и внедрения новых методов.
Итак, да, тот же принцип применяется к библиотекам OO.