Golang - Decorator Pattern

Golang - Decorator Pattern

The Decorator Pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

The "Gang of Four" patterns book describes the Decorator Pattern as "attaching additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality."

Example

In Golang, the Decorator Pattern can be implemented using interfaces and anonymous functions. Let's see a code example:

package main

import "fmt"

type Printer interface {
    Print() string
}

type SimplePrinter struct {}

func (sp *SimplePrinter) Print() string {
    return "Hello, world!"
}

func BoldDecorator(p Printer) Printer {
    return PrinterFunc(func() string {
        return "<b>" + p.Print() + "</b>"
    })
}

type PrinterFunc func() string

func (pf PrinterFunc) Print() string {
    return pf()
}

func main() {
    simplePrinter := &SimplePrinter{}
    boldPrinter := BoldDecorator(simplePrinter)

    fmt.Println(simplePrinter.Print()) // Output: Hello, world!
    fmt.Println(boldPrinter.Print()) // Output: <b>Hello, world!</b>
}

In the code example above, we declare a Printer interface and a SimplePrinter struct that implements the Print() method.

Then we define the BoldDecorator function that receives a Printer interface and returns another Printer interface. This function wraps the original Print() method in a new one that returns the same value enclosed in <b> tags.

This is a simple example, but it shows the power of the Decorator Pattern. By adding a new decorator, we can change the behavior of an object at runtime without changing its original code.

The Decorator Pattern is especially useful when we have to add new functionality to an object that already exists and we want to keep its original code untouched. In this way, we can avoid creating new subclasses for every new feature we want to add.

Middleware Decorator

Another example where we can use the Decorator Pattern is in the implementation of middleware in web frameworks. Middleware functions are functions that execute before or after a request is handled by a web server. They can be used for tasks such as authentication, logging, and caching.

In Golang, we can use the Decorator Pattern to implement middleware functions. We can define a Handler interface that represents a function that handles an HTTP request and returns an HTTP status code and a response body. Then we can define a Middleware interface that receives a Handler and returns another Handler that executes some additional behavior before or after the original Handler is called.

Here's an example:

package main

import (
    "fmt"
    "net/http"
)

type Handler func(r *http.Request) (int, string)

type Middleware func(h Handler) Handler

func LoggingMiddleware(h Handler) Handler {
    return func(r *http.Request) (int, string) {
        fmt.Println("Handling request...")
        status, body := h(r)
        fmt.Printf("Request handled with status %d\\\\n", status)
        return status, body
    }
}

func main() {
    helloHandler := func(r *http.Request) (int, string) {
        return http.StatusOK, "Hello, world!"
    }

    loggingHandler := LoggingMiddleware(helloHandler)

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        status, body := loggingHandler(r)
        w.WriteHeader(status)
        w.Write([]byte(body))
    })

    http.ListenAndServe(":8080", nil)
}

In this example, we define a Handler function that returns an HTTP status code and a response body. Then we define a LoggingMiddleware function that receives a Handler and returns another Handler that logs the incoming request and the outgoing response.

Finally, in the main() function, we create a new http.HandleFunc that receives a logging Handler and returns an HTTP response with the same status code and body returned by the Handler.

Conclusion

In conclusion, the Decorator Pattern is a powerful design pattern that can be used in many contexts to create more flexible and extensible code. In Golang, this pattern can be implemented using interfaces and anonymous functions, and it can be used to add new functionality to an existing codebase without affecting its original code.

I hope you found this article useful! If you have any questions or suggestions, please leave a comment below.


References

Did you find this article valuable?

Support Matthias Bruns by becoming a sponsor. Any amount is appreciated!