Functional equivalent of decorator pattern?

后端 未结 10 2091
悲&欢浪女
悲&欢浪女 2021-02-02 06:34

What is the functional programming equivalent of the decorator design pattern?

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

相关标签:
10条回答
  • 2021-02-02 07:12

    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.

    0 讨论(0)
  • 2021-02-02 07:14

    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
    
    0 讨论(0)
  • 2021-02-02 07:15

    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.

    0 讨论(0)
  • 2021-02-02 07:18

    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.

    0 讨论(0)
  • 2021-02-02 07:22

    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.

    0 讨论(0)
  • 2021-02-02 07:23

    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"
    
    0 讨论(0)
提交回复
热议问题