Функциональный эквивалент шаблона декоратора? - PullRequest
40 голосов
/ 15 августа 2011

Что такое функциональный программный эквивалент шаблона проектирования декоратора?

Например, как бы вы написали этот конкретный пример в функциональном стиле?

Ответы [ 10 ]

33 голосов
/ 15 августа 2011

В функциональном программировании вы бы обернули данную функцию в новую функцию.

Чтобы дать надуманный пример 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)))

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

15 голосов
/ 15 августа 2011

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

Если бы C ++ / Java / C # / любой другой практически идентичный язык имел встроенную в язык функцию художественного оформления, вы бы не думали об этом как о шаблоне. Так уж вышло, что «шаблоны» - это шаблоны для структурирования систем в ранних, обязательных, ориентированных на цель языках, обычно без автобоксирования и с относительно тонкими протоколами для корневого класса.

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

11 голосов
/ 15 августа 2011

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

Простой пример в 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 - связанный пример оборачивает обработку параметров для запроса html вокруг любого существующего обработчика.

6 голосов
/ 29 ноября 2011

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

Интерфейс окна

В Haskell есть классы, которые имеютвсе функциональные возможности интерфейса, а затем некоторые.Поэтому мы будем использовать следующий класс Haskell:

class Window w where
  draw :: w -> IO ()
  description :: w -> String

Абстрактный класс WindowDecorator

Этот вариант немного сложнее, поскольку в Haskell нет понятия наследования.Обычно мы вообще не предоставляем этот тип и позволяем декораторам напрямую реализовывать Window, но давайте полностью последуем примеру.В этом примере 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
6 голосов
/ 15 августа 2011

Примерно так:

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"
2 голосов
/ 22 августа 2011

Радость Clojure говорит об этой самой проблеме в главе 13.3, «Отсутствие шаблонов проектирования». Согласно JoC, макросы -> и ->> в некоторой степени аналогичны шаблону декоратора.

2 голосов
/ 15 августа 2011

Хорошо, прежде всего попробуем найти все основные компоненты шаблона декоратора относительно ООП. Этот шаблон в основном используется для украшения, то есть добавления дополнительных функций к существующему объекту . Это простейшее из возможных определений этого паттерна. Теперь, если мы попытаемся найти те же компоненты, которые есть в этом определении, в мире FP, мы можем сказать, что дополнительные функции = новые функции и объект не существует в FP, скорее FP имеет то, что вы называете данные или структура данных в различных формах . Таким образом, в терминах FP этот шаблон становится добавлением дополнительных функций для структур данных FP или улучшением существующей функции некоторыми дополнительными функциями.

1 голос
/ 15 августа 2011

Я не уверен на 100%, но я думаю, что серия лекций C9 по расширенному функциональному программированию действительно хорошо объясняет проблему.

Помимо этого вы можете использовать ту же самую технику внутри F # (она поддерживает тот же механизм ОО), и в этом особом случае я бы так и сделал.

Полагаю, это вопрос вкуса и проблемы, которую вы пытаетесь решить.

0 голосов
/ 21 мая 2019
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 #

Это моя попытка создать что-то разумное в F #, так как вы попросили много примеров.Я немного ржавый, так что, надеюсь, никто не посрамит меня: P.Декоратор в основном требует двух частей, нового поведения и новых данных.Новые поведения чрезвычайно просты в функциональных языках, поскольку они являются «просто еще одной функцией», поскольку функции неотделимы от объектов.Новые данные на самом деле так же просты, и есть несколько способов добиться этого, самый простой из которых - кортеж.Вы можете видеть, что я создал новый тип данных, который является расширенным набором предыдущего, и я вызвал существующую функцию для этого существующего поведения.Таким образом, наши старые данные и старое поведение все еще соблюдаются, но у нас также есть новое поведение и новые данные.

0 голосов
/ 29 ноября 2011

Вот пример использования 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"]
     };
  }

Разумеется, промежуточное программное обеспечение может быть сложено (например, Lint(Logger(ContentLength(app))))

...