Notes: Go Design Patterns - Decorator Pattern

Notes: Go Design Patterns - Decorator Pattern

This blog contains decorator design pattern implementation using an improved standard library logger

ยท

2 min read

Table of contents

No heading

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]))
}
ย