与装饰器设计模式等效的函数式编程是什么?
例如,您如何以功能样式编写this particular example?
答案 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#中创建明智的尝试,因为您询问了许多示例。我有点生锈,所以希望没人会羞辱我:P。装饰器基本上需要两个部分,新行为和新数据。在函数式语言中,新行为极其容易,因为它们只是“另一个函数”,因为函数与对象固有地是分离的。实际上,新数据同样也很容易,您可以通过多种方法来实现,最简单的方法就是元组。您可以看到我创建了一个新数据类型,它是先前数据类型的超集,并且我已经为该现有行为调用了现有函数。因此,我们仍然尊重旧数据和旧行为,但是也有了新行为和新数据。