Notes: Go Design Patterns - Decorator Pattern
This blog contains decorator design pattern implementation using an improved standard library logger
Table of contents
No headings in the article.
Decorator pattern allows a user to add new functionality to an existing object without altering its structure. This pattern creates a decorator object that wraps the original object and provides additional functionality keeping the object method's signature intact. This is a structural pattern.
Here we'll use an improved logger wrapping standard logger to add color functionality based on the level of log e.g. blue color for warning, yellow color for error, etc.
First, we create a decorator object which wraps the standard logger:
type ImprovedLogger struct {
Logger *log.Logger
}
Now, we add new functionality via a method that adds color to logs:
func (l *ImprovedLogger) ColoredOutputf(logType LogLevelType, buf *bytes.Buffer, format string, args ...interface{}) {
fmt.Print(string(colorMap[logType]))
l.Logger.Printf(format, args...)
fmt.Print(buf)
buf.Reset()
}
You can see that inside we used the standard library method l.Logger.Printf(format, args...)
but added an additional functionality on top of it. This kind of implementation gives us the flexibility to add methods to the objects without modifying the object directly.
Complete working code where you can see the complete result:
package main
import (
"bytes"
"fmt"
"log"
)
type LogLevelType string
type color string
const (
Default LogLevelType = "default"
Debug LogLevelType = "debug"
Error LogLevelType = "error"
Fatal LogLevelType = "fatal"
Info LogLevelType = "info"
Warn LogLevelType = "warn"
)
var colorMap = map[LogLevelType]color{
Default: "\033[0m",
Debug: "\033[37m",
Error: "\033[33m",
Fatal: "\033[31m",
Info: "\033[32m",
Warn: "\033[36m",
}
type ImprovedLogger struct {
Logger *log.Logger
}
// new functionality
func (l *ImprovedLogger) ColoredOutputf(logType LogLevelType, buf *bytes.Buffer, format string, args ...interface{}) {
fmt.Print(string(colorMap[logType]))
l.Logger.Printf(format, args...)
fmt.Print(buf)
buf.Reset()
}
// modified existing functionality
func (l *ImprovedLogger) SetPrefix(service string, prefix string) {
l.Logger.SetPrefix(service + " " + prefix)
}
// constructor
func NewImprovedLogger(buf *bytes.Buffer, prefix string, flag int, service string) *ImprovedLogger {
logger := log.New(buf, prefix, flag)
improvedLogger := &ImprovedLogger{
Logger: logger,
}
// adding service name is mandatory
improvedLogger.SetPrefix(service, prefix)
return improvedLogger
}
func main() {
var buf bytes.Buffer
newLogger := NewImprovedLogger(&buf, "logger: ", log.Lshortfile, "(order service)")
newLogger.ColoredOutputf(Warn, &buf, "warning")
newLogger.ColoredOutputf(Error, &buf, "error")
newLogger.ColoredOutputf(Info, &buf, "info")
fmt.Print(string(colorMap[Default]))
}