Как стать автором
Обновить

Функциональное и объектно-ориентированное проектирование

Уровень сложностиСредний
Время на прочтение6 мин
Количество просмотров6.6K
Движение вверх и вниз
Движение вверх и вниз

В современном обучении программированию, как правило, основное внимание уделяется парадигме объектно-ориентированного программирования (OOP) и вытекающей из неё методологии объектно-ориентированного проектирования (OOD). Определённый ренессанс в наше время испытывает парадигма функционального программирования (FP), но практически никогда в связке с ней не рассматривается функциональное проектирование (FD). Попытаемся осветить наше видение этих вопросов.

Замечание о термине "функциональное"

Сразу же определимся с терминологией. Некоторые педантичные читатели, возможно, захотят сделать замечание, что те функции, которые имеются в виду в "функциональном проектировании" (то есть задачи системы) – это не те функции, которые имеются в виду в "функциональном программировании" (то есть формализм лямбда-выражений). На наш взгляд, напротив, речь здесь идёт о глубоко взаимосвязанных между собой вещах, а именно о денотационной семантике программного кода, с одной стороны, и его синтаксисе, с другой. Для целей нашего изложения примем, что функциональные требования к программе – это и есть денотационная семантика, которой программа должна обладать, либо же её часть (так как семантика программы может быть не полностью специфицирована в требованиях).

Объектно-ориентированное проектирование

Методология объектно-ориентированного проектирования, опирающаяся на парадигму объектно-ориентированного программирования, сводится вот к чему. Мы подвергаем проектируемую систему декомпозиции, то есть логически представляем её в виде суммы отдельных частей, взаимодействующих между собой. Считается кошерным, если эта декомпозиция производится не абы как, а по определённым шаблонам, а именно с помощью описанных в литературе нескольких десятков паттернов проектирования (точное количество и состав паттернов отличается в зависимости от конкретной школы OOD). В ряде школ OOD требуется, чтобы деление на классы соответствовало физической структуре предметной области.

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

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

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

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

Функциональное проектирование

Функциональное проектирование происходит в точности противоположным образом. Мы сосредотачиваемся на стратегии, которая будет наполнением нашей высокоуровневой функции, и думаем о том, как построить эту стратегию из элементарных кирпичиков, представляющих собой стандартные элементы языка программирования (условно говоря, элементарные функции). Если этих элементарных функций нам не хватает (как обычно и происходит), мы комбинируем их между собой, синтезируя функции более высокого уровня, представляющие собой семантическую абстракцию более высокого порядка, чем наш исходный язык программирования, часто называемую DSL (domain-specific language), то есть предметно-ориентированный язык. Рано или поздно мы поднимаемся в нашей метаязыковой абстракции до того уровня, когда конструкции DSL позволяют нам описать нашу стратегию в одной или нескольких функциях верхнего уровня.

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

Вместо декомпозиции в данном случае в качестве основной методики проектирования применяется семантическая абстракция, то есть синтез.

Некоторые авторы рассматривают FD как специфический вид декомпозиции – функциональную декомпозицию:

Функциональная декомпозиция в представлении некоторых авторов
Функциональная декомпозиция в представлении некоторых авторов

Мы считаем это неверным. Функции невозможно декомпозировать в строгом смысле из-за наличия системного эффекта. Туловище, руки, ноги и голова способны совместно выполнять функции человека только в морге при прощании, да и по отдельности друг от друга не ходят и не говорят.

Методы программирования сверху-вниз и снизу-вверх

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

Хотя зачастую программирование на функциональных языках тяготеет к методу снизу-вверх благодаря удобству работы с отдельными функциями в цикле интерпретатора REPL, но то же самое можно сказать и о таком классическом объектном языке, как Smalltalk.

Пример с хабра

В одном комментарии на хабре мы почерпнули хорошую иллюстрацию сказанного. Представим, что мы ничего не знаем о написании шахматных программ и хотим методами OOD реализовать такую шахматную программу. Тогда мы выделим объекты в виде шахматной доски и 32 фигур (которые очень удачно сводятся всего к шести классам), релизуем у фигур свойство "цвет" и методы "ход" и "взятие"... и всё. И дальше совершенно непонятно, чьим, собственно, методом должна быть стратегия игры. На практике, наверное, она в таком случае будет реализована в виде методов доски, реализуя антипаттерн "суперкласс", и весь OOD на этом закончится.

Именно таким объектным образом, однако, реализуется тактика юнитов в классических стратегических играх вроде Warcraft. Каждый юнит воюет сам за себя. Это нормально и даже полезно для непритязательного ИИ игры, который может быть обыгран не очень продвинутым игроком, но обычная война, как легко заметить, происходит совсем не так. Реальные военнослужащие в успешно воюющей армии в основном исполняют планы командиров, а не занимаются ситуационным реагированием каждый за себя (и, кстати, не являются клонами друг друга).

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

Социальные проблемы функционального проектирования

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

Поучительно будет привести следующую историю.

В начале 1980-х годов Дуглас Ленат из Техаса разработал программу EURISCO, написанную на языке Лисп и использовавшую глубокую интроспекцию для модификации своего собственного кода с целью реализации искусственного интеллекта. EURISCO автоматически выдвигала различные предположения (эвристики) о том, как наиболее эффективно решать поставленную задачу, и пыталась применить их на практике. Если эвристика давала хороший результат, то она оставалась в коде и развивалась дальше, если плохой – то удалялась из кода программы (нечто подобное популярно описал Пелевин в "iPhuck 10"). Ленат с EURISCO участвовал в американском чемпионате по военно-морской игре Traveller TCS и выиграл чемпионат в 1981 году. Тогда правила игры изменили, но EURISCO снова выиграла чемпионат в 1982 году, после чего Лената забанили. Суть военно-морской стратегии EURISCO заключалась в том, чтобы не строить большие корабли, а сделать москитный флот из маленьких слабых корабликов, которые, однако, крайне сложно уничтожить во всей совокупности. В 1980-х над этой стратегией посмеялось и игровое сообщество, и профессиональные военные, как над неким артефактом несовершенства правил игры. А сейчас мы видим, как она применяется в реальной жизни в войне воздушных и морских дронов. Не прошло и 50 лет, как люди дозрели до идей искусственного интеллекта, работавшего на машине примерно с 256 килобайтами памяти и процессором примерно на 1 МГц.

Ленат умер 31 августа 2023 года, вероятно успев увидеть торжество идей своей программы.

Выводы

Программируете с использованием тех инструментов, которыми владеете.

Если вы владеете как объектно-ориентированной, так и функциональной парадигмой, оценивайте характер сложности своей задачи. Если сложность тактическая, используйте OOD и выполняйте декомпозицию. Если сложность стратегическая, используйте FD и стройте DSL. Если завал везде, попробуйте FD на верхнем уровне и OOD на нижнем.

Теги:
Хабы:
+18
Комментарии90

Публикации

Работа

Ближайшие события