开发者

Functional equivalent of decorator pattern?

What is the functional programming equivalent of the decorator开发者_如何学Python design pattern?

For example, how would you write this particular example in a functional style?


In functional programming, you would wrap a given function in a new function.

To give a contrived Clojure example similar to the one quoted in your question:

My original drawing function:

(defn draw [& args]
  ; do some stuff 
  )

My function wrappers:

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

These return a new function that can be used anywhere the original drawing function is used, but also draw the scrollbars.


Currying functional parameters / composition is the closest equivalent. However, it's a mistake to even ask this question, because patterns exist to compensate for weaknesses in the host language.

If C++/Java/C#/any other practically identical language had a decoration feature built into the language, you wouldn't think of it as a pattern. It just so happens that "patterns" are patterns for structuring systems in early-bound imperative objective-oriented languages, usually without autoboxing, and with relatively thin protocols for the root class.

Edit: Also a lot of these are studied as patterns in these languages because there's no obvious built in higher order functions, higher order typing, and the type systems are relatively useless. Clearly, that's not a universal problem with these languages, but at the time these patterns started to be codified those issues were present.


You can "decorate" functions by wrapping them inside other functions, typically using some form of higher order function to perform the wrapping.

Simple example in 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)

This technique is used extensively in functional libraries. A good example is wrapping web request handlers using Ring middleware - the linked example wraps parameter handling for html request around any existing handler.


Something like this:

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"


In Haskell, this OO pattern translates pretty much directly, you only need a dictionary. Note that a direct translation is not actually a good idea. Trying to force a OO concept into Haskell is kind of backwords, but you asked for it so here it is.

The Window Interface

Haskell has classes, which has all the functionality of an Interface and then some. So we will use the following Haskell class:

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

The Abstract WindowDecorator class

This one is a bit more tricky since Haskell has no concept of inheritance. Usually we would not provide this type at all and let the decorators implement Window directly, but lets follow the example completely. In this example, a WindowDecorator is a window with a constructor taking a window, lets augment this with a function giving the decorated window.

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

Note that we provide a default implementation of Window, it can be replaced, and all instances of WindowDecorator will be a Window.

The decorators

Making decorators can then be done as follows:

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

Finishing Up

Finally we can define some windows:

data SimpleWindow = SimpleWindow ...

instance Window SimpleWindow where
   draw = simpleDraw
   description = simpleDescription

makeSimpleWindow :: SimpleWindow
makeSimpleWindow = ...

makeSimpleVertical = VerticalScrollWindow . makeSimpleWindow
makeSimpleHorizontal = HorizontalScrollWindow . makeSimpleWindow
makeSimpleBoth = VerticalScrollWindow . HorizontalScrollWindow . makeSimpleWindow


Ok, first of all lets try to find all the main components of decorator pattern in respect to OOP. This pattern is basically used to decorate i.e add additional features to an existing object. This is the simplest possible definition of this pattern. Now if we try to find the same components that are there in this definition in the world of FP, we can say that additional features = new functions and object are not there in FP, rather FP has what you call data or data structure in various forms. So in FP terms this patterns becomes, adding additional functions for FP data structures or enhancing existing function with some additional features.


The Joy of Clojure talks about this very issue in chapter 13.3, "A lack of design patterns". According to the JoC, the -> and ->> macros are somewhat analogous to the decorator pattern.


I'm not 100% sure but I think the C9 lecture series on advanced functional programming explains the problem really good.

Aside from this you can use just the same technique inside F# (it supports just the same OO mechanism) and in this special case I would do so.

I guess it's a matter of tast and the problem you are trying to solve.


Here's an example using JSGI, a web server API for 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"]
     };
  }

Compliant middleware can be stacked, of course (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# attempt

This is my attempt at creating something sensible in F# since you asked for many examples. I'm a little rusty so hopefully nobody will shame me :P. The decorator basically requires two parts, new behaviors and new data. New behaviors are exceedingly easy in functional languages as they are "just another function", since functions are inherently decoupled from objects. New data is actually similarly easy and there's several ways you can achieve this, the simplest being a tuple. You can see I've created a new datatype that is a superset of the previous, and I've called the existing function for that existing behavior. So we have our old data and old behavior still respected, but we also have new behavior and new data.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜