Golang Decorator Pattern

Posted by Henry Du on Wednesday, January 5, 2022

Golang Decorator Pattern

Decorator pattern is the one of twenty-three well-known design patter from Gang of Four design patterns. It is categorized in structural design pattern, along with Adapter pattern, Proxy pattern etc. The decorator pattern allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

It tries to resolve the problem that single responsibility, following the Single Responsibility Principle, should be added to or removed from an object dynamically at run-time.

It is a flexible alternative to subclassing for extended functionalities due to

  • Inheritance is static. We can’t alter the behavior of an existing object at runtime.
  • Subclasses can have just one parent class.
  • The most important, Golang doesn’t support inheritance.

Functional Options Pattern

In fact, the famous Golang Functional Options Pattern is similar with the Decorator pattern. For example, we have a concrete Server object with several configurations.

type Server struct {
	fqdn string
	port uint32
	timeout time.Duration
	maxConn uint32
}

For each configuration, it is a single responsibility to set the value. If the applications want to initialize different servers, one with timeout value, the other with the max connection, we need to create different NewServer function for those applications.

Alternatively, we could use Functional options pattern by providing the extended functionalities for each configuration.

type optionalFunc func(*Server)

func NewServer(options ...optionalFunc) *Server {
	svr := &Server{}
	for _, opt := range options {
		opt(svr)
	}
	return svr
}

func WithHost(fqdn string) optionalFunc {
	return func(s *Server) {
    	s.fqdn = fqdn
  	}
}

func WithPort(port uint32) optionalFunc {
	return func(s *Server) {
    	s.port = port
  	}
}

func WithTimeout(timeout time.Duration) optionalFunc {
	return func(s *Server) {
		s.timeout = timeout
	}
}

func WithMaxConn(maxConn uint32) optionalFunc {
	return func(s *Server) {
    	s.maxConn = maxConn
  	}
}

If the application needs the server with timeout only, we could define it as follows

svr := NewServer(
	WithHost("localhost"),
	WithPort(3030),
	WithTimeout(time.Minute),
)

If the other application needs the server with max connection only, then we could define it as follows

svr := NewServer(
	WithHost("localhost"),
	WithPort(3030),
	WithMaxConn(10),
)

HTTP Routing

Assuming there is a requirement that, when HTTP server receives the requests, for each URL path, there will be different handler combinations. For example:

  • /v1/get: set server header, set auth cookie, get
  • /v2/get: set server header, do basic auth, get
  • /v3/get: set server header, do basic auth, enable debug log, get

However, net/http package only provide one HTTP handler function, defined as

func http.HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
http.HandleFunc on pkg.go.dev

HandleFunc registers the handler function for the given pattern in the DefaultServeMux. The documentation for ServeMux explains how patterns are matched.

Here comes decorator pattern combined with functional optional pattern. We could define a HTTPHandlerDecorator.

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 order
		h = d(h)
	}
	return h
}

Then, we could have the following functions for each purpose.

func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", "v1.0")
		h(w, r)
	}
}

func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		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) {
		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.Debug(r.Form)
		log.Debugf("path %s", r.URL.Path)
		h(w, r)
	}
}

func Get(w http.ResponseWriter, r *http.Request) {
	w.Write("Server Response")
}

Finally, we are able to handle different requirements from different URL path.

func main() {
	http.HandleFunc("/v1/get", Handler(Get, WithAuthCookie, WithServerHeader))
	http.HandleFunc("/v2/get", Handler(Get, WithBasicAuth, WithServerHeader))
	http.HandleFunc("/v3/get", Handler(Get, WithDebugLog, WithBasicAuth, WithServerHeader))
	if err := http.ListenAndServer(":8080", nil); err != nil {
		log.Fatalf("ListenAndServer: %v", err)
	}
}

Pizza Topping

The decorator pattern is alternatively called wrapper. Using decorators we can wrap objects countless number of times since both target objects and decorators follow the same interface.

We can use pizza and its toppings as an example. The Hawaiian pizza may be “wrapped” by pineapple topping, then, it may be “wrapped” by cheese topping.

  1. The Component declares the common interface for both wrappers and wrapped objects.
type Pizza interface {
    GetPrice() int
}
  1. Concrete Component is a class of objects being wrapped. It defines the basic behavior, which can be altered by decorators. The Base Decorator class has a field for referencing a wrapped object. The field’s type should be declared as the component interface so it can contain both concrete components and decorators. The base decorator delegates all operations to the wrapped object.
type Hawaiian struct {}

func (h *Hawaiian) GetPrice() int {
    return 15
}
  1. Concrete Decorators define extra behaviors that can be added to components dynamically. Concrete decorators override methods of the base decorator and execute their behavior either before or after calling the parent method.
type PineappleTopping struct {
    pizza Pizza
}

func (p *PineappleTopping) GetPrice() int {
    pizzaPrice := p.pizza.GetPrice()
    return pizzaPrice + 6
}

type CheeseTopping struct {
    pizza Pizza
}

func (c *CheeseTopping) GetPrice() int {
    pizzaPrice := c.pizza.GetPrice()
    return pizzaPrice + 10
}
  1. The Client can wrap components in multiple layers of decorators, as long as it works with all objects via the component interface.
func main() {

    pizza := &Hawaiian{}

    //Add cheese topping
    pizzaWithCheese := &CheeseTopping{
        pizza: pizza,
    }

    //Add pineapple topping
    pizzaWithCheeseAndPineapple := &PineappleTopping{
        pizza: pizzaWithCheese,
    }

    fmt.Printf("Price of Hawaiian with pineapple and cheese topping is %d\n", pizzaWithCheeseAndPineapple.GetPrice())
}

I have demonstrated three Golang examples which are able to use the decorator pattern. It is widely used in configuration settings, event handlers and wrapped objects.

Reference

  1. Decorator pattern wiki
  2. Refactor Guru: Decorator Pattern