功能相当于装饰模式?

时间:2011-08-15 11:26:46

标签: design-patterns haskell f# functional-programming clojure

与装饰器设计模式等效的函数式编程是什么?

例如,您如何以功能样式编写this particular example

10 个答案:

答案 0 :(得分:33)

在函数式编程中,您可以将给定函数包装在新函数中。

给出一个类似于你问题中引用的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)))

这些返回一个新函数,可以在使用原始绘图函数的任何地方使用,但也可以绘制滚动条。

答案 1 :(得分:14)

曲线功能参数/组成是最接近的等价物。然而,甚至提出这个问题是错误的,因为存在模式来弥补宿主语言中的弱点。

如果C ++ / Java / C#/任何其他几乎相同的语言都有内置于该语言的装饰功能,您就不会将其视为一种模式。事实上,“模式”是用于在早期命令式面向目标语言中构建系统的模式,通常没有自动装箱,并且具有相对较薄的根类协议。

编辑:还有很多这些作为这些语言中的模式进行研究,因为没有明显内置的高阶函数,更高阶的类型,类型系统相对无用。显然,这不是这些语言的普遍问题,但在这些模式开始编纂时,这些问题就出现了。

答案 2 :(得分:11)

您可以通过将函数包装在其他函数中来“装饰”函数,通常使用某种形式的高阶函数来执行包装。

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)

该技术广泛用于功能库中。一个很好的例子是wrapping web request handlers using Ring middleware - 链接的示例包装了围绕任何现有处理程序的html请求的参数处理。

答案 3 :(得分:6)

这样的事情:

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"

答案 4 :(得分:6)

在Haskell中,这个OO模式几乎直接翻译,你只需要一本字典。请注意,直接翻译实际上并不是一个好主意。试图强迫一个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

答案 5 :(得分:2)

好的,首先让我们尝试找到与OOP相关的装饰模式的所有主要组件。此模式主要用于装饰,即将附加功能添加到现有对象。这是该模式最简单的定义。现在,如果我们试图找到FP世界中这个定义中的相同组件,我们可以说FP中没有附加功能=新功能和对象,而FP有你的意思以各种形式调用数据或数据结构。因此,在FP术语中,这些模式变为,为FP数据结构添加附加功能或通过一些附加功能增强现有功能。

答案 6 :(得分:2)

Joy of Clojure在第13.3章“缺乏设计模式”中讨论了这个问题。根据JoC,->->>宏有点类似于装饰器模式。

答案 7 :(得分:1)

我不是100%肯定,但我认为C9 lecture series on advanced functional programming解释问题非常好。

除此之外,您可以在F#中使用相同的技术(它只支持相同的OO机制),在这种特殊情况下,我会这样做。

我想这是一个尝试和你想要解决的问题。

答案 8 :(得分:0)

以下是使用JSGI(JavaScript的Web服务器API)的示例:

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)))

答案 9 :(得分:0)

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。装饰器基本上需要两个部分,新行为和新数据。在函数式语言中,新行为极其容易,因为它们只是“另一个函数”,因为函数与对象固有地是分离的。实际上,新数据同样也很容易,您可以通过多种方法来实现,最简单的方法就是元组。您可以看到我创建了一个新数据类型,它是先前数据类型的超集,并且我已经为该现有行为调用了现有函数。因此,我们仍然尊重旧数据和旧行为,但是也有了新行为和新数据。

相关问题