Decorator functions in Go

20.3k views Asked by At

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".

3

There are 3 answers

11
mkopriva On BEST ANSWER

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

6
stevenferrer On

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

4
syntagma On

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, ")}...)