Table of Contents
Decorator Pattern
What is the Decorator Pattern?
The Decorator pattern is a design pattern that allows us to dynamically add behavior to an object at runtime without altering its implementation. This is achieved by creating a wrapper object or decorator that contains the original object and provides an enhanced interface to add new behavior.
In Go, we can use functions as decorators because Go supports higher-order functions, which means functions can be passed as parameters and returned as values.
An HTTP-related Example
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("Received 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) } }
In this code, we use the Decorator pattern. The WithServerHeader()
function acts as a decorator that takes an http.HandlerFunc and returns a modified version. This example is relatively simple, as we only add a response header using WithServerHeader().
Pipeline of Multiple Decorators
When using multiple decorators, the code can become less visually appealing as we need to nest functions layer by layer. However, we can refactor the code to make it cleaner. To do this, we first write a utility function that iterates through and calls each decorator:
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 }
Then, we can use it like this:
http.HandleFunc("/v4/hello", Handler(hello, WithServerHeader, WithBasicAuth, WithDebugLog))