Являются ли первоклассные функции заменой шаблону Стратегии?

15

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

Например, скажем, вы хотели передать функциональность в объект. В Java вам нужно передать объекту другой объект, который инкапсулирует желаемое поведение. В таком языке, как Ruby, вы просто передаете функциональность в виде анонимной функции.

Однако я думал об этом и решил, что, возможно, стратегия предлагает больше, чем просто анонимная функция.

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

В объектно-ориентированном языке, который поддерживает первоклассные функции, имеет ли шаблон стратегии какое-либо преимущество перед использованием функций?

Авив Кон
источник
10
«Однако анонимная функция сама по себе может содержать состояние, которое перестает существовать в тот момент, когда функция завершает выполнение». Это неверно: закрытие может содержать состояние, которое существует в разных вызовах.
Джорджио
«Однако анонимная функция сама по себе может содержать состояние, которое перестает существовать в тот момент, когда функция завершает выполнение». : не забывая глобальные переменные и, по крайней мере, статические переменные.
gbjbaanb
5
Связанный: c2.com/cgi/wiki?ClosuresAndObjectsAreEquivalent
Euphoric

Ответы:

13

Когда язык поддерживает ссылки на функции ( Java поддерживает начиная с версии 8 ), они часто являются хорошей альтернативой для стратегий, потому что они обычно выражают одно и то же с меньшим синтаксисом. Однако в некоторых случаях реальный объект может быть полезен.

Интерфейс Стратегии может иметь несколько методов. Давайте возьмем интерфейс в RouteFindingStragegyкачестве примера, который инкапсулирует различные алгоритмы поиска маршрута. Это может объявить методы, такие как

  • Route findShortestRoute(Node start, Node destination)
  • boolean doesRouteExist(Node start, Node destination)
  • Route[] findAllPossibleRoutes(Node start, Node destination)
  • Route findShortestRouteToClosestDestination(Node start, Node[] destinations)
  • Route findTravelingSalesmanRoute(Node[] stations)

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

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

Philipp
источник
2
«когда это внутреннее состояние становится очень сложным, часто становится полезным содействовать закрытию полноценного класса»: почему? Если внутреннее состояние становится сложным, вы также можете поместить его в объект / запись, которая хранится внутри замыкания.
Джорджио
1
@Giorgio Но тогда вам нужно будет поддерживать две синтаксические сущности - замыкание и класс, который управляет его внутренним состоянием. Таким образом, код можно упростить, переместив замыкание в этот класс. Это может быть лучше, а может и нет, что зависит от конкретного варианта использования, языка программирования и личных предпочтений.
Филипп
Это кажется мне разумным.
Джорджио
5

Неверно, что анонимная функция может содержать состояние, которое перестает существовать, когда функция завершает выполнение.

Возьмите следующий пример в Common Lisp:

(defun number-strings (ss)
  (let ((counter 0))
    (mapcar #'(lambda (s) (format nil "~a: ~a" (incf counter) s)) ss)))

Эта функция берет список строк и добавляет счетчик к каждому элементу списка. Так, например, ссылаясь

(number-strings '("a" "b" "c"))

дает

("1: a" "2: b" "3: c")

Функция number-stringsвнутренне использует анонимную функцию с переменной, counterкоторая хранит состояние (текущее значение счетчика), которое повторно используется каждый раз, когда вызывается функция.

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

В частности, шаблон стратегии требует объект только с одним методом, поэтому замыкание должно делать эту работу. Но, как заметил Филипп в своем ответе, в зависимости от обстоятельств (сложного состояния) и языков программирования вы можете получить более элегантное решение, используя объекты.

Джорджио
источник
Таким образом, в языке, который поддерживает функции первого класса как замыкания, вы все еще будете использовать «классическую» стратегию?
Авив Кон
1
Я склонен согласиться с Филиппом: это зависит от языка и личных предпочтений. Я бы всегда выбирал подход, который делает запись максимально простой. Например, в Лиспе я мог определить свое состояние как список переменных через a, letа затем определить свое замыкание внутри него. По сути, я бы определил объект одним методом на лету. На другом языке (например, Java) может быть более удобно (синтаксически проще) определить надлежащий объект для хранения состояния. Итак, я бы решил от случая к случаю.
Джорджио
1

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

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

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

Карл Билефельдт
источник
«Если вам нужно отслеживать состояние в функциональной программе, вы не изменяете закрытую переменную, даже если язык это позволяет». Я фанат чисто функционального стиля и согласен с вашим советом. С другой стороны, замыкания являются не только функциональной конструкцией, не говоря уже о чисто функциональной. Идея закрытия переменных из лексического контекста ортогональна ссылочной прозрачности / неизменности.
Джорджио
Извините, но я не могу следовать вашей аргументации. Какое отношение имеет обработка состояния в чисто функциональном программировании к рассматриваемому вопросу?
Филипп
1
Суть в том, что если вы используете одну часть функциональной парадигмы, например, функции первого класса, не удивляйтесь, если вам нужно задействовать другие части парадигмы, чтобы она работала гладко.
Карл Билефельдт
1

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

Термин стратегия в основном используется в разговорах с другими программистами , чтобы лаконично выразить ваше намерение. В этом нет ничего волшебного.

idoby
источник
2
В этом вопросе конкретно упоминается шаблон разработки стратегии, имеющий определенную структуру классов. Другое значение «стратегии» как плана действий, предназначенного для достижения конкретной цели, в этом контексте является неточным.
Я склонен согласиться с @Snowman. Вы уверены, что говорите о шаблоне стратегии ?
Роуэн Фриман
1
@ Снеговик, даже на странице, на которую вы ссылаетесь, не указано, как именно этот шаблон должен быть реализован, скорее, они дают примеры на конкретных языках, но на диаграмме UML нет ничего, что говорит о том, что мне нужно использовать наследование C ++, интерфейсы Java или блоки Ruby , Поэтому я не согласен с вашим анализом.
idoby