Подтвердить что ты не робот

Функциональный эквивалент шаблона декоратора?

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Функциональные параметры/состав Currying являются ближайшим эквивалентом. Тем не менее, это ошибка, чтобы даже задать этот вопрос, потому что существуют шаблоны для компенсации слабых мест на языке хозяина.

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

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

Ответ 3

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

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

Ответ 4

Что-то вроде этого:

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"

Ответ 5

В 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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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. Декоратор в основном требует двух частей, нового поведения и новых данных. Новые поведения чрезвычайно просты в функциональных языках, поскольку они являются "просто еще одной функцией", поскольку функции неотделимы от объектов. Новые данные на самом деле так же просты, и есть несколько способов добиться этого, самый простой из которых - кортеж. Вы можете видеть, что я создал новый тип данных, который является расширенным набором предыдущего, и я вызвал существующую функцию для этого существующего поведения. Таким образом, наши старые данные и старое поведение все еще соблюдаются, но у нас также есть новое поведение и новые данные.