Что такое функциональный программный эквивалент шаблона проектирования декоратора?
Например, как бы вы написали этот конкретный пример в функциональном стиле?
Что такое функциональный программный эквивалент шаблона проектирования декоратора?
Например, как бы вы написали этот конкретный пример в функциональном стиле?
В функциональном программировании вы переносите данную функцию в новую функцию.
Чтобы дать надуманный пример Clojure, похожий на тот, который указан в вашем вопросе:
Моя первоначальная функция рисования:
(defn draw [& args]
; do some stuff
)
Мои обертки функций:
; Add horizontal scrollbar
(defn add-horizontal-scrollbar [draw-fn]
(fn [& args]
(draw-horizontal-scrollbar)
(apply draw-fn args)))
; Add vertical scrollbar
(defn add-vertical-scrollbar [draw-fn]
(fn [& args]
(draw-vertical-scrollbar)
(apply draw-fn args)))
; Add both scrollbars
(defn add-scrollbars [draw-fn]
(add-vertical-scrollbar (add-horizontal-scrollbar draw-fn)))
Они возвращают новую функцию, которая может использоваться везде, где используется исходная функция чертежа, но также рисует полосы прокрутки.
Функциональные параметры/состав Currying являются ближайшим эквивалентом. Тем не менее, это ошибка, чтобы даже задать этот вопрос, потому что существуют шаблоны для компенсации слабых мест на языке хозяина.
Если С++/Java/С#/любой другой практически идентичный язык имел функцию украшения, встроенную в язык, вы бы не подумали об этом как о шаблоне. Так получилось, что "шаблоны" являются шаблонами для структурирования систем в ранних обязательных объектно-ориентированных языках, обычно без автобоксинга, и с относительно тонкими протоколами для корневого класса.
Edit: Также многие из них изучаются как шаблоны на этих языках, потому что нет очевидных встроенных функций более высокого порядка, написания более высокого порядка, а системы типов относительно бесполезны. Ясно, что это не универсальная проблема с этими языками, но в то время, когда эти шаблоны начали кодифицироваться, эти проблемы присутствовали.
Вы можете "украсить" функции, обернув их внутри других функций, обычно используя некоторую форму функции более высокого порядка для выполнения обертывания.
Простой пример в Clojure:
; define a collection with some missing (nil) values
(def nums [1 2 3 4 nil 6 7 nil 9])
; helper higher order function to "wrap" an existing function with an alternative implementation to be used when a certain predicate matches the value
(defn wrap-alternate-handler [pred alternate-f f]
(fn [x]
(if (pred x)
(alternate-f x)
(f x))))
; create a "decorated" increment function that handles nils differently
(def wrapped-inc
(wrap-alternate-handler nil? (constantly "Nil found!") inc))
(map wrapped-inc nums)
=> (2 3 4 5 "Nil found!" 7 8 "Nil found!" 10)
Этот метод широко используется в функциональных библиотеках. Хорошим примером является обертывание обработчиков веб-запросов с использованием Ring middleware - связанный пример обертывает обработку параметров для запроса html вокруг любого существующего обработчика.
Что-то вроде этого:
class Window w where
draw :: w -> IO ()
description :: w -> String
data VerticalScrollingWindow w = VerticalScrollingWindow w
instance Window w => Window (VerticalScrollingWindow w) where
draw (VerticalScrollingWindow w)
= draw w >> drawVerticalScrollBar w -- `drawVerticalScrollBar` defined elsewhere
description (VerticalScrollingWindow w)
= description w ++ ", including vertical scrollbars"
В Haskell этот шаблон OO транслируется в значительной степени напрямую, вам нужен только словарь. Обратите внимание, что прямой перевод на самом деле не является хорошей идеей. Попытка заставить OO-концепцию в Haskell является своего рода backwords, но вы просили об этом, так что вот оно.
Интерфейс окна
В Haskell есть классы, которые имеют все функциональные возможности интерфейса, а затем некоторые. Поэтому мы будем использовать следующий класс Haskell:
class Window w where
draw :: w -> IO ()
description :: w -> String
Абстрактный класс WindowDecorator
Это немного сложнее, поскольку у Haskell нет концепции наследования. Обычно мы не предоставляли этот тип вообще, и пусть декораторы реализуют Window
напрямую, но позволяют полностью следовать примеру. В этом примере a WindowDecorator
- это окно с конструктором, берущим окно, позволяет увеличить его с помощью функции, предоставляющей украшенное окно.
class WindowDecorator w where
decorate :: (Window a) => a -> w a
unDecorate :: (Window a) => w a -> a
drawDecorated :: w a -> IO ()
drawDecorated = draw . unDecorate
decoratedDescription :: w a -> String
decoratedDescription = description . unDecorate
instance (WindowDecorator w) => Window w where
draw = drawDecorated
description = decoratedDescription
Обратите внимание, что мы предоставляем стандартную реализацию Window
, ее можно заменить, и все экземпляры WindowDecorator
будут Window
.
Декораторы
Создание декораторов может быть выполнено следующим образом:
data VerticalScrollWindow w = VerticalScrollWindow w
instance WindowDecorator VerticalScrollWindow where
decorate = VerticalScrollWindow
unDecorate (VerticalScrollWindow w ) = w
drawDecorated (VerticalScrollWindow w ) = verticalScrollDraw >> draw w
data HorizontalScrollWindow w = HorizontalScrollWindow w
instance WindowDecorator HorizontalScrollWindow where
decorate = HorizontalScrollWindow
unDecorate (HorizontalScrollWindow w .. ) = w
drawDecorated (HorizontalScrollWindow w ..) = horizontalScrollDraw >> draw w
Окончание
Наконец, мы можем определить некоторые окна:
data SimpleWindow = SimpleWindow ...
instance Window SimpleWindow where
draw = simpleDraw
description = simpleDescription
makeSimpleWindow :: SimpleWindow
makeSimpleWindow = ...
makeSimpleVertical = VerticalScrollWindow . makeSimpleWindow
makeSimpleHorizontal = HorizontalScrollWindow . makeSimpleWindow
makeSimpleBoth = VerticalScrollWindow . HorizontalScrollWindow . makeSimpleWindow
Хорошо, прежде всего, попытайтесь найти все основные компоненты шаблона декоратора в отношении ООП. Этот шаблон в основном используется для украшения, добавляет дополнительные функции к существующему объекту . Это самое простое определение этого шаблона. Теперь, если мы попытаемся найти те же компоненты, которые есть в этом определении в мире FP, можно сказать, что дополнительные функции = новые функции, а объект отсутствует в FP, скорее у FP есть то, что вы вызывать данные или структуру данных в различных формах. Таким образом, в терминах FP эти шаблоны становятся, добавляя дополнительные функции для структур данных FP или улучшая существующую функцию с некоторыми дополнительными функциями.
Радость Clojure рассказывает об этой самой проблеме в главе 13.3 "Отсутствие шаблонов проектирования". Согласно JoC, ->
и ->>
макросы несколько аналогичны шаблону декоратора.
Я не уверен на 100%, но я думаю, что серия C9 для расширенного функционального программирования объясняет проблему действительно хорошей.
Кроме того, вы можете использовать только ту же технику внутри F # (она поддерживает только тот же механизм OO), и в этом специальном случае я бы сделал это.
Я думаю, это вопрос вкуса и проблема, которую вы пытаетесь решить.
Вот пример использования JSGI, API веб-сервера для JavaScript:
function Log(app) {
return function(request) {
var response = app(request);
console.log(request.headers.host, request.path, response.status);
return response;
};
}
var app = Logger(function(request) {
return {
status: 200,
headers: { "Content-Type": "text/plain" },
body: ["hello world"]
};
}
Соответствующее промежуточное ПО может быть уложено, конечно (e.x. Lint(Logger(ContentLength(app)))
)
type Window = {Description: string}
type HorizontalScroll = {HPosition: int}
type VerticalScroll = {VPosition: int}
type Feature =
| HorizontalScroll of HorizontalScroll
| VerticalScroll of VerticalScroll
let drawWithFeatures w (f: Feature) =
match f with
| HorizontalScroll h -> {Description= w.Description + "Horizontal"}
| VerticalScroll v -> {Description= w.Description + "Vertical"}
type FeatureTwo = Red of int| Blue of int | Green of int | Feature of Feature
let rec drawWithMoreFeatures (w: Window) (ft:FeatureTwo)=
match ft with
| Red x -> {Description = w.Description + "Red"}
| Green x -> {Description = w.Description + "Green"}
| Blue x -> {Description = w.Description + "Blue"}
| Feature f -> drawWithFeatures w f
let window = {Description = "Window title"}
let horizontalBar = HorizontalScroll {HPosition = 10}
let verticalBar = VerticalScroll {VPosition = 20}
[Red 7; Green 3; Blue 2; Feature horizontalBar; Feature verticalBar] |> List.fold drawWithMoreFeatures window
Это моя попытка создать что-то разумное в F #, так как вы попросили много примеров. Я немного ржавый, так что, надеюсь, никто не посрамит меня: P. Декоратор в основном требует двух частей, нового поведения и новых данных. Новые поведения чрезвычайно просты в функциональных языках, поскольку они являются "просто еще одной функцией", поскольку функции неотделимы от объектов. Новые данные на самом деле так же просты, и есть несколько способов добиться этого, самый простой из которых - кортеж. Вы можете видеть, что я создал новый тип данных, который является расширенным набором предыдущего, и я вызвал существующую функцию для этого существующего поведения. Таким образом, наши старые данные и старое поведение все еще соблюдаются, но у нас также есть новое поведение и новые данные.