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
- “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm