17

Decorator pattern (functions) has many benefits:

It is very useful when a method has many orthogonal concerns... I.e., None of these concerns are related, other than that we wanna do all (or some) of them whenever we call our method. This is where the decorator pattern really helps.

By implementing the decorator pattern we subscribe to the open-closed principal. Our method is open to future extension but closed to future modification. There's a lot of groovy benefits to obeying the open-closed principle.

However, all the examples that I found are really complicated (e.g., writing HTTP servers with many middlewares). This make it difficult for me to apply the principle elsewhere. I need something that I can easily try on so as to wrap my head around it.

Can someone give me an simpler example that can best illustrate how to do Decorator pattern (functions) in Go please?

This example by Alex Alehano, is too simple to be put into practical use. I need something that can illustrate this:

func Decorate(c Decorated, ds ...Decorator) Decorated {
    decorated := c
    for _, decorate := range ds {
        decorated = decorate(decorated)
    }
    return decorated
}

A string manipulation according to different option/instruction, e.g., to upper, to lower, to base64, etc, would be the best example IMO, and adding prefix/suffix too, as "This technique proves especially valuable if the decorators themselves are parameterized".

Community
  • 1
  • 1
xpt
  • 20,363
  • 37
  • 127
  • 216
  • 1
    A simple Go example of a decorator pattern and 23 other kinds of GoF design patterns can be found here https://github.com/Margular/hello-design-pattern. –  Sep 08 '20 at 08:07

3 Answers3

25

First of all, a decorator is basically a function that takes another function of a specific type as its argument and returns a function of the a same type. This essentially allows you to create a chain of functions. So in Go it would look something like this:

// this is the type of functions you want to decorate
type StringManipulator func(string) string

// this is your decorator.
func ToLower(m StringManipulator) StringManipulator {
    return func(s string) string {
        lower := strings.ToLower(s)
        return m(lower)
    }
}

here's a more complete example

UPDATE:

and here's a revised example that the applying order of fn3 and fn4 are the same

xpt
  • 20,363
  • 37
  • 127
  • 216
mkopriva
  • 35,176
  • 4
  • 57
  • 71
  • This is exactly the Decorator functions pattern in Go that I was looking for thanks a lot! – xpt Aug 29 '17 at 18:43
  • If I understand correctly, `AppendDecorator` as it is defined, can only be used to append as the first operation? – RayfenWindspear Aug 29 '17 at 18:58
  • https://play.golang.org/p/rrBnAJOIc2 no, `AppendDecorator` is a little different from the other ones in that you need to call it to return the actual decorator, which than gets called with the function you want to decorate. Basically it just creates a closure for the string you pass to `AppendDecorator` and returns a new decorator which then has access to that string... – mkopriva Aug 29 '17 at 18:59
  • @RayfenWindspear probably a name like `AppendDecoratorFactory` would be more appropriate. – mkopriva Aug 29 '17 at 19:06
  • Aha, that makes sense now. I wasn't looking closely enough at the return type. Thanks. AHHH don't use the word Factory regarding Go!! Evil Java voodoo! – RayfenWindspear Aug 29 '17 at 19:06
  • @mkopriva, can you help make the following code work please? https://play.golang.org/p/3ox9dYJGMk I.e., I added `func Decorate` and want to make it works. Thanks. – xpt Aug 29 '17 at 20:25
  • Also, for the expression `AppendDecorator(" -GOLANG")(ToLower(PrependDecorator("DECORATED: ", fn3))`, intuitive thinking is that the inner most function get applied first, however, the test result show that it is actually the outer most function get applied first. That's totally against my intuition. Can you explain a bit why `ToLower` is applied to `AppendDecorator` but not the `PrependDecorator`, which is the inner function, please? Thx – xpt Aug 29 '17 at 20:34
  • If you use the decorator pattern as is demonstrated in my answer you need to **pass a function to the decorators**. This gets weird when you want to loop over `StringManipulator`s, because the "decorators" aren't "manipulators" they just return them, so you need to pass a manipulator to the decorator to get the decorated manipulator... if it sounds confusing, that's because it is :) here's the example https://play.golang.org/p/WzbN0dUEQA and give me a second i'll fix it to something less cryptic. – mkopriva Aug 29 '17 at 20:42
  • So, [here you create a slice of decorators, as opposed to manipulators](https://play.golang.org/p/YWySMO61os). – mkopriva Aug 29 '17 at 20:57
  • @xpt here's a demonstration of the order https://play.golang.org/p/zj-wnlXS2O hope that helps to explain it a little. – mkopriva Aug 29 '17 at 21:29
  • Thanks a lot @mkopriva! Your answer is a masterpiece that I'll surely come back and revisit again and again whenever I need to do this, and I truly hope that anyone who is benefit from it will upvote it. Oh, BTW, interesting to see that the order was different whether ToLower is applied to AppendDecorator or PrependDecorator first. No wonder I saw one `Decorate()` function loop is in reverse order. PS. like that "_different take_" better! – xpt Aug 30 '17 at 00:41
7

A string manipulation according to different option/instruction, e.g., to upper, to lower, adding prefix/suffix, to base64, etc, would be the best example IMO.

This is the example you asked about:

package main

import (
    "fmt"
    "strings"
)

const (
    prefix = "PREFIX"
    suffix = "SUFFIX"
)

type Decorated=string

func addConstPrefix(s string) string {
    return prefix + s
}

func addConstSuffix(s string) string {
    return s + suffix
}

type Decorator=func(string) string

func Decorate(c Decorated, ds ...Decorator) Decorated {
    decorated := c
    for _, decorator := range ds {
        decorated = decorator(decorated)
    }
    return decorated
}

func main() {

    var toLower Decorator = strings.ToLower
    var toUpper Decorator = strings.ToUpper
    var dec3 Decorator = addConstPrefix
    var dec4 Decorator = addConstSuffix

    input := "Let's decorate me!"
    inputUppercase := Decorate(input, []Decorator{toUpper}...)
    fmt.Println(inputUppercase)

    allDecorators := []Decorator{toUpper, toLower, dec3, dec4}
    output := Decorate(input, allDecorators...)
    fmt.Println(output)
}

Note that for the sake of simplicity, it uses Golang's type aliases, which were introduced in Go 1.9. These are the two aliases we are using:

type Decorated=string
type Decorator=func(string)string

so that your Decorate() function could later be applied. Then we are simply creating few decorators (functions) which match the type signature we defined:

var toLower Decorator = strings.ToLower
var toUpper Decorator = strings.ToUpper
var dec3 Decorator = addConstPrefix
var dec4 Decorator = addConstSuffix

and apply them:

allDecorators := []Decorator{toUpper, toLower, dec3, dec4}
output := Decorate(input, allDecorators...)

EDIT:

A parametrized decorator would simply be a function which returns another function, for example:

func addPrefix(prefix string) func(string) string {
    return func(s string) string {
        return prefix + s
    }
}

It could then be applied in the following way:

input2 := "Let's decorate me!"
prefixed := Decorate(input2, []Decorator{addPrefix("Well, ")}...)
syntagma
  • 23,346
  • 16
  • 78
  • 134
  • @srxf type aliases were just added in Go 1.9, released last week. – Adrian Aug 29 '17 at 18:18
  • Upvoting! however I was looking for the Decorator functions pattern in the form of `return func(s string) string` etc. and moreover, examples for "[_This technique proves especially valuable if the decorators themselves are parameterized_](http://xion.io/post/code/go-decorated-functions.html)" – xpt Aug 29 '17 at 18:46
  • Thanks for the update on the _parametrized decorator_. Looking at the code again now, I think your example is more suitable as the example that is easier to understand. Had you put that in first, I would have chosen yours as the answer. Now I wish I could choose both. Thanks again for your valuable help! – xpt Aug 29 '17 at 20:46
  • Hi @syntagma, FYI, I put your demo code together at https://play.golang.org/p/s4tWssqb_3 and I was looking for reason why your `Decorator` function applying order is not reversed when I realized that, strictly speaking, what you've coded is not the Decorator functions pattern that everyone else is talking about. The best article that explains this Decorator functions pattern for Go is http://xion.io/post/code/go-decorated-functions.html, for your convenience. – xpt Aug 30 '17 at 01:17
4

I know a very good example of decorators/middlewares that utilizes the same technique that you demonstrated. This is not specific to string manipulation though, but I find it really beautifully crafted, here it is (scan through the code):

https://github.com/basvanbeek/pubsub/blob/master/kafka/publisher.go

Take a look at the type PublisherOption func(*publisherConfig) error

You will notice that some of the functions returns PublisherOption type. These are the decorators/middleware.

The action happens on NewKafkaPublisher function:

func NewKafkaPublisher(
  broker, topic string,
  options ...PublisherOption,
  ) (pubsub.Publisher, error) {
  if len(strings.Trim(broker, " \t")) == 0 {
      return nil, ErrNoBrokers
  }
  brokerHosts := strings.Split(broker, ",")

  if len(topic) == 0 {
      return nil, ErrNoTopic
  }
  // set sensible defaults
  pc := &publisherConfig{
    syncPublisher: defaultSyncPublisher,
    ackMode:       defaultRequiredAcks,
    successes:     nil,
    logger:        log.NewNopLogger(),
    topic:         topic,
    partitioner:   sarama.NewManualPartitioner(topic),
    partition:     0,
  }

 // parse optional parameters
 for _, option := range options {
     if err := option(pc); err != nil {
        return nil, err
     }
 }

Notice that it loops through the option and calls each decorator/middleware.

Supplemental example is here: https://gist.github.com/steven-ferrer/e4c1eb973a930c2205039448cda6d3d9

Runnable code: https://play.golang.org/p/ZXixnyTHXH

Hope that helped :)

stevenferrer
  • 2,504
  • 1
  • 22
  • 33
  • The difficult for me is to apply the principle elsewhere. Looking at the code you quoted unfortunately doesn't give me a better understanding of it and the sense of how to _apply_ it for my own problems. It is still too complicated for me to wrap my head around it. – xpt Aug 29 '17 at 17:43
  • I've created a simple example, please see this: https://gist.github.com/steven-ferrer/e4c1eb973a930c2205039448cda6d3d9 – stevenferrer Aug 29 '17 at 18:05
  • Thanks for your help. Upvoated! – xpt Aug 29 '17 at 18:49
  • Glad It helped :) – stevenferrer Aug 29 '17 at 18:51
  • 1
    Very interesting example, I thought they were called closures. I thought a decorator function would take a function as parameter and return a wrapped function of the same type, although I guess naming of patterns doesn't fully transfer between different langs. – Davos May 08 '19 at 09:34
  • 2
    @Davos, tbh, i'm not completely sure now if this is good example of decorator pattern :) I always look at the accepted answer if I want to refresh my knowledge – stevenferrer May 08 '19 at 18:03