Welcome to our website.

Understanding the Decorator Pattern in Go

image

I’ve always liked the decorator pattern. It lets you attach one function to another through a higher-order function, which can simplify code and make small, focused pieces of logic much more reusable. Functions become easier to combine, almost like Lego blocks.

In Go, decorators are really just another way to apply functional programming ideas. If you already think in terms of passing functions around and composing behavior, the pattern feels familiar. That said, Go is not Python, and it is not Java either. It does not provide much syntax sugar, and because it is a statically typed compiled language without a VM, you can’t write decorators in quite the same elegant way those languages allow.

Even so, the pattern is still useful in Go. It just looks a little more explicit.

A minimal decorator example

Start with the simplest possible case:

package main

import "fmt"

func decorator(f func(s string)) func(s string) {

    return func(s string) {
        fmt.Println("Started")
        f(s)
        fmt.Println("Done")
    }
}

func Hello(s string) {
    fmt.Println(s)
}

func main() {
        decorator(Hello)("Hello, World!")
}

The decorator() function is a higher-order function: it takes a function as input and returns a new function. The returned anonymous function runs its own logic before and after calling the original Hello() function.

This is conceptually very close to how decorators work in Python. The obvious difference is that Go has no @decorator syntax. So the call site is more awkward.

If readability matters, you can at least split it up a bit:

hello := decorator(Hello)
hello("Hello")

That is still straightforward enough, and it makes the composition more visible.

Decorating functions to measure execution time

A more practical example is timing function execution. Here is a decorator for two sum functions:

package main

import (
  "fmt"
  "reflect"
  "runtime"
  "time"
)

type SumFunc func(int64, int64) int64

func getFunctionName(i interface{}) string {
  return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

func timedSumFunc(f SumFunc) SumFunc {
  return func(start, end int64) int64 {

    defer func(t time.Time) {
      fmt.Printf("--- Time Elapsed (%s): %v ---\n",
          getFunctionName(f), time.Since(t))
    }(time.Now())

    return f(start, end)
  }
}

func Sum1(start, end int64) int64 {
  var sum int64
  sum = 0
  if start > end {
    start, end = end, start
  }
  for i := start; i <= end; i++ {
    sum += i
  }
  return sum
}

func Sum2(start, end int64) int64 {
  if start > end {
    start, end = end, start
  }
  return (end - start + 1) * (end + start) / 2
}

func main() {

  sum1 := timedSumFunc(Sum1)
  sum2 := timedSumFunc(Sum2)

  fmt.Printf("%d, %d\n", sum1(-10000, 10000000), sum2(-10000, 10000000))
}

A few details matter here:

  1. There are two implementations of summation. Sum1() uses a loop, while Sum2() uses a mathematical formula. The code also handles cases where start and end may be negative or reversed.
  2. Reflection is used to obtain the function name.
  3. The actual decorator is timedSumFunc().

When run, the output looks like this:

$ go run time.sum.go
--- Time Elapsed (main.Sum1): 3.557469ms ---
--- Time Elapsed (main.Sum2): 291ns ---
49999954995000, 49999954995000

This is a nice example of why decorators are handy: you can add timing logic around an existing function without touching the function’s core implementation.

Using decorators with HTTP handlers

Decorator-style composition fits HTTP code especially well. Middleware in Go often follows exactly this shape.

Here is a simple HTTP server that adds a response header:

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Println("--->WithServerHeader()")
        w.Header().Set("Server", "HelloServer v0.0.1")
        h(w, r)
    }
}

func hello(w http.ResponseWriter, r *http.Request) {
    log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
    fmt.Fprintf(w, "Hello, World! "+r.URL.Path)
}

func main() {
    http.HandleFunc("/v1/hello", WithServerHeader(hello))
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

WithServerHeader() accepts an http.HandlerFunc and returns a modified version of it. The handler’s behavior is extended without changing hello() itself.

Once you have one decorator like this, it is natural to write more: one for response headers, one for authentication cookies, one for cookie validation, one for request logging, and so on.

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Println("--->WithServerHeader()")
        w.Header().Set("Server", "HelloServer v0.0.1")
        h(w, r)
    }
}

func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Println("--->WithAuthCookie()")
        cookie := &http.Cookie{Name: "Auth", Value: "Pass", Path: "/"}
        http.SetCookie(w, cookie)
        h(w, r)
    }
}

func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Println("--->WithBasicAuth()")
        cookie, err := r.Cookie("Auth")
        if err != nil || cookie.Value != "Pass" {
            w.WriteHeader(http.StatusForbidden)
            return
        }
        h(w, r)
    }
}

func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Println("--->WithDebugLog")
        r.ParseForm()
        log.Println(r.Form)
        log.Println("path", r.URL.Path)
        log.Println("scheme", r.URL.Scheme)
        log.Println(r.Form["url_long"])
        for k, v := range r.Form {
            log.Println("key:", k)
            log.Println("val:", strings.Join(v, ""))
        }
        h(w, r)
    }
}
func hello(w http.ResponseWriter, r *http.Request) {
    log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
    fmt.Fprintf(w, "Hello, World! "+r.URL.Path)
}

func main() {
    http.HandleFunc("/v1/hello", WithServerHeader(WithAuthCookie(hello)))
    http.HandleFunc("/v2/hello", WithServerHeader(WithBasicAuth(hello)))
    http.HandleFunc("/v3/hello", WithServerHeader(WithBasicAuth(WithDebugLog(hello))))
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

This style is powerful because each decorator does one small thing. The handler logic remains focused, while cross-cutting concerns are assembled around it.

Turning multiple decorators into a pipeline

Nesting decorators works, but once there are several layers, the call chain starts to look noisy. Deeply wrapped handlers are harder to scan.

A small refactoring can improve that. Define a shared decorator type and a helper function that applies decorators in sequence:

type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc

func Handler(h http.HandlerFunc, decors ...HttpHandlerDecorator) http.HandlerFunc {
    for i := range decors {
        d := decors[len(decors)-1-i] // iterate in reverse
        h = d(h)
    }
    return h
}

Now the registration becomes cleaner:

http.HandleFunc("/v4/hello", Handler(hello,
                WithServerHeader, WithBasicAuth, WithDebugLog))

This reads better and makes the pipeline structure much more obvious. Instead of mentally parsing nested function calls, you can see the stack of behaviors in one place.

The awkward part: generic decorators in Go

There is still an obvious limitation in Go’s decorator pattern: reusability across unrelated function signatures.

Take the timing example above. timedSumFunc() only works for SumFunc, because the decorator is tightly coupled to that specific function type. That is the main pain point. If a decorator cannot be made reasonably generic, then its usefulness is narrower than it first appears.

This is where Go’s static typing becomes both a strength and a constraint. Python can do this easily because it is dynamic. Java can lean on its runtime and VM. Go requires function types to be known at compile time.

Still, Go does give you two tools that can be pushed pretty far: interface{} and reflection. With those, it is possible to build a more generic decorator.

Here is a reflection-based version. For readability, error handling has been omitted:

func Decorator(decoPtr, fn interface{}) (err error) {
    var decoratedFunc, targetFunc reflect.Value

    decoratedFunc = reflect.ValueOf(decoPtr).Elem()
    targetFunc = reflect.ValueOf(fn)

    v := reflect.MakeFunc(targetFunc.Type(),
            func(in []reflect.Value) (out []reflect.Value) {
                fmt.Println("before")
                out = targetFunc.Call(in)
                fmt.Println("after")
                return
            })

    decoratedFunc.Set(v)
    return
}

The key piece is reflect.MakeFunc(), which creates a new function value with the same type as the original target function. Inside that generated function, targetFunc.Call(in) invokes the wrapped function.

This decorator takes two parameters:

  • decoPtr: an output parameter that will receive the decorated function
  • fn: the original function to decorate

Yes, it looks a little clumsy. That is the trade-off here.

Assume these are the functions we want to decorate:

func foo(a, b, c int) int {
    fmt.Printf("%d, %d, %d \n", a, b, c)
    return a + b + c
}

func bar(a, b string) string {
    fmt.Printf("%s, %s \n", a, b)
    return a + b
}

One way to use Decorator() is to declare a matching function type first:

type MyFoo func(int, int, int) int
var myfoo MyFoo
Decorator(&myfoo, foo)
myfoo(1, 2, 3)

That works, but it does not feel very generic if you still have to define a function signature explicitly.

Another option is to reuse an existing function variable instead:

mybar := bar
Decorator(&mybar, bar)
mybar("hello,", "world!")

It is not especially pretty, but it does work.

And that really sums up the decorator pattern in Go: useful, expressive, and sometimes slightly awkward. The language supports the idea, but not with the same elegance you get from languages that have richer syntax sugar or a more flexible runtime.

If Go ever adds more convenience in this area, decorators would become much easier to write and much nicer to use.

Related Posts