Я нашел эту цитату в « Радости Clojure » на с. 32, но кто-то сказал мне то же самое за ужином на прошлой неделе, и я слышал это и в других местах:
Недостатком объектно-ориентированного программирования является тесная связь между функцией и данными.
Я понимаю, почему ненужное соединение плохо в приложении. Также я могу с уверенностью сказать, что изменяемого состояния и наследования следует избегать даже в объектно-ориентированном программировании. Но я не понимаю, почему прикрепление функций к классам изначально плохо.
Я имею в виду, добавление функции к классу похоже на пометку почты в Gmail или наложение файла в папке. Это организационная техника, которая помогает вам снова ее найти. Вы выбираете некоторые критерии, а затем складываете все вместе. До ООП наши программы были довольно большими пакетами методов в файлах. Я имею в виду, вы должны поместить функции где-то. Почему бы не организовать их?
Если это скрытая атака на типы, почему бы им просто не сказать, что ограничение типа ввода и вывода функцией является неправильным? Я не уверен, смогу ли я согласиться с этим, но по крайней мере я знаком с аргументами за и против безопасности типа. Это звучит для меня как отдельная проблема.
Конечно, иногда люди ошибаются и ставят функциональность не на тот класс. Но по сравнению с другими ошибками это кажется очень незначительным неудобством.
Итак, у Clojure есть пространства имен. Чем наложение функции на класс в ООП отличается от наложения функции на пространство имен в Clojure и почему это так плохо? Помните, что функции в классе не обязательно работают только с членами этого класса. Посмотрите на java.lang.StringBuilder - он работает с любым ссылочным типом или через автобокс с любым типом вообще.
PS Эта цитата ссылается на книгу, которую я не читал: Программирование мультипарадигмы в Леде: Тимоти Бадд, 1995 .
Ответы:
Теоретически, слабая связь функций с данными облегчает добавление большего количества функций для работы с одними и теми же данными. Недостатком является то, что это затрудняет изменение самой структуры данных, поэтому на практике хорошо разработанный функциональный код и хорошо разработанный код ООП имеют очень похожие уровни связи.
Возьмем ориентированный ациклический граф (DAG) в качестве примера структуры данных. В функциональном программировании вам все еще нужна некоторая абстракция, чтобы избежать повторения, поэтому вы создадите модуль с функциями для добавления и удаления узлов и ребер, поиска узлов, достижимых из данного узла, создания топологической сортировки и т. Д. Эти функции эффективно тесно связаны с данными, даже если компилятор не применяет их. Вы можете добавить узел трудным путем, но зачем вам это нужно? Связность внутри одного модуля предотвращает жесткое сцепление всей системы.
Наоборот, на стороне ООП любые функции, кроме базовых операций DAG, будут выполняться в отдельных классах «представления», причем объект DAG передается в качестве параметра. Так же просто добавить столько представлений, сколько вы хотите, которые работают с данными DAG, создавая тот же уровень развязки данных функций, что и в функциональной программе. Компилятор не помешает вам собрать все в один класс, но ваши коллеги это сделают.
Изменение парадигм программирования не меняет лучшие практики абстракции, сплоченности и связывания, оно просто меняет то, какие практики компилятор помогает вам применять. В функциональном программировании, когда вы хотите связать функцию-данные, это обеспечивается джентльменским соглашением, а не компилятором. В ООП разделение вида модели обеспечивается джентльменским соглашением, а не компилятором.
источник
В случае, если вы не знали, что это уже принимается: понимание объектно-ориентированных и замыканий - это две стороны одной медали. Тем не менее, что такое закрытие? Он берет переменную (и) или данные из окружающей области видимости и привязывается к ней внутри функции, или с точки зрения OO вы фактически делаете то же самое, когда вы, например, передаете что-то в конструктор, чтобы позже вы могли использовать это часть данных в функции-члене этого экземпляра. Но брать вещи из окружающей области - не очень хорошая вещь - чем больше окружающая область, тем больше зла это делать (хотя с практической точки зрения, какое-то зло часто необходимо для выполнения работы). Использование глобальных переменных доводит это до крайности, когда функции в программе используют переменные в области действия программы - действительно очень плохо. Естьхорошие описания в другом месте о том, почему глобальные переменные являются злом.
Если вы следуете методам ОО, вы в основном уже признаете, что каждый модуль в вашей программе будет иметь определенный минимальный уровень зла. Если вы применяете функциональный подход к программированию, вы стремитесь к идеалу, когда ни один модуль в вашей программе не будет содержать зло замыкания, хотя у вас все еще может быть немного, но оно будет намного меньше, чем ОО.
Это недостаток ОО - он поощряет такого рода зло, соединение данных для функционирования через стандартное закрытие (своего рода теория программирования с разбитыми окнами ).
Единственным плюсом является то, что, если вы знали, что для начала будете использовать множество замыканий, ОО, по крайней мере, предоставляет вам идеологическую структуру, помогающую организовать такой подход, чтобы обычный программист мог его понять. В частности, закрываемые переменные явны в конструкторе, а не просто взяты неявно в замыкании функции. Функциональные программы, которые используют много замыканий, часто более загадочны, чем эквивалентные ОО-программы, хотя не обязательно менее элегантны :)
источник
Это о типе сцепления:
Функция, встроенная в объект для работы с этим объектом, не может использоваться для других типов объектов.
В Haskell вы пишете функции для работы с классами типов - поэтому существует множество различных типов объектов, с которыми может работать любая данная функция, при условии, что это тип данного класса, с которым работает функция.
Отдельно стоящие функции позволяют такое разделение, которое вы не получите, когда сосредоточитесь на написании своих функций для работы внутри типа A, потому что тогда вы не сможете использовать их, если у вас нет экземпляра типа A, даже если функция может в противном случае достаточно универсальны для использования с экземпляром типа B или экземпляром типа C.
источник
В Java и подобных воплощениях ООП методы экземпляра (в отличие от свободных функций или методов расширения) не могут быть добавлены из других модулей.
Это становится большим ограничением, когда вы рассматриваете интерфейсы, которые могут быть реализованы только методами экземпляра. Вы не можете определить интерфейс и класс в разных модулях, а затем использовать код из третьего модуля, чтобы связать их вместе. Более гибкий подход, такой как классы типов Haskell, должен быть в состоянии сделать это.
источник
Ориентация на объект - это, в основном, процедурная абстракция данных (или функциональная абстракция данных, если убрать побочные эффекты, которые являются ортогональной проблемой). В некотором смысле, лямбда-исчисление является старейшим и наиболее чистым объектно-ориентированным языком, поскольку оно обеспечивает только абстракцию функциональных данных (поскольку у него нет никаких конструкций, кроме функций).
Только операции одного объекта могут проверять представление данных этого объекта. Даже другие объекты того же типа не могут этого сделать. (Это основное различие между объектно-ориентированной абстракцией данных и абстрактными типами данных: с помощью ADT объекты одного типа могут проверять представление данных друг друга, только представление объектов других типов скрыто.)
Это означает, что несколько объектов одного типа могут иметь разные представления данных. Даже один и тот же объект может иметь разные представления данных в разное время. (Например, в Scala
Map
s иSet
s переключаются между массивом и хеш-кодом в зависимости от количества элементов, потому что для очень малых чисел линейный поиск в массиве быстрее, чем логарифмический поиск в дереве поиска из-за очень малых постоянных факторов .)С внешней стороны объекта вы не должны, вы не можете знать его представление данных. Это противоположность жесткой связи.
источник
Тесная связь между данными и функциями плохая, потому что вы хотите иметь возможность менять каждую независимо от другой, а тесная связь делает это трудным, потому что вы не можете изменить одну, не зная и, возможно, не изменяя другую.
Вы хотите, чтобы различные данные, представляемые в функцию, не требовали каких-либо изменений в функции, и аналогичным образом вы хотите иметь возможность вносить изменения в функцию, не требуя каких-либо изменений в данных, с которыми она работает, для поддержки этих изменений функции.
источник