Decorator Design Pattern in Go!

9 June, 2024

0

0

0

Note: Throughout this blog post, we use Go idiomatic terminology to describe concepts and code examples. This ensures consistency with Go's conventions and helps maintain readability for Go developers.

What is the Decorator Design Pattern?

  • The Decorator Design Pattern in Go is a structural pattern that allows you to dynamically add behavior to an object without affecting the behavior of other objects from the same type
  • This pattern is particularly useful for extending the functionality of structs in a flexible and reusable way

Why use the Decorator Design Pattern in Go?

  • Flexible and Extensible Design:
    • Easily Add New Features: Add new behavior to structs without changing their original code
    • Runtime Extension: Modify behavior at runtime as needed
  • Adherence to Open/Closed Principle:
    • Open for Extension: Add new functionality through decorators
    • Closed for Modification: Original structs or interfaces remain unchanged
  • Composability:
    • Layer Behaviors: Combine multiple decorators to build complex functionality from simple components
    • Clean Code: Keeps code modular and easy to maintain
  • Separation of Concerns:
    • Single Responsibility: Each decorator handles a specific feature or behavior
    • Maintainable Codebase: Easier to understand, test, and maintain
  • Dynamic Behavior Addition:
    • No Inheritance Needed: Since Go doesn’t use inheritance, decorators allow adding behavior dynamically
    • Flexible Combinations: Apply different combinations of behaviors to structs without creating many subclasses
  • Non-intrusive:
    • No Codebase Changes: Add new behaviors without modifying existing code
    • Safe and Isolated: Useful when you can’t or don’t want to change the original code
  • Reusable Code:
    • Reusable Components: Decorators can be reused across different projects
    • Reduce Duplication: Minimize code duplication by reusing decorators

Key Participants in Decorator Design Pattern Implementation

  • Component Interface : This is the interface that defines the operations that can be altered by decorators.
  • Concrete Component: This is the initial object that implements the component interface, this is a struct that implements the methods defined in the component interface. It contains the basic functionality that can be extended by decorators.
  • Decorator: This maintains a reference to a component object and defines methods that conform to the component's interface, the decorator struct can delegate method calls to the original component while also adding its own behavior. This allows the decorator to add behavior to the component without changing the component's code.
  • Concrete Decorators: Concrete Decorators extend the functionality of a component by adding new behavior. They wrap the original component and modify or enhance its behavior without altering its structure.
  • Client: The client is the part of the code that uses the component interface to interact with objects that implement this interface. It is not concerned with how the objects are implemented, allowing for flexible and interchangeable use of different components and decorators.

Key participant interactions

How: Real World Example!

Lets consider how the decorator pattern could be implemented for a real world example Logging

Scenario: Imagine you are developing a web application with a logging system. You want to ensure that logs are informative and easy to read. After adding the same, you realize that just logging messages is not enough. You want to add more context to the logs without changing the existing simple logger implementation.

Decorator Analogy: We should be able to extend the existing simple logger to add each log message with a timestamp or a prefix indicating the log level (e.g., DEBUG, INFO, ERROR).

Lets define the participants based on above example:

  • Component Interface:
    • Definition: Defines the interface for objects that can log messages.
    • Example: Logger interface.
  • Concrete Component:
    • Definition: Implements the basic logging functionality.
    • Example: SimpleLogger struct.
  • Decorator:
    • Definition: Maintains a reference to a Logger object and implements the Logger interface.
    • Example: LoggerDecorator struct.
  • Concrete Decorators:
    • Definition: Extend the functionality of the Logger by adding new behavior such as timestamping or adding a prefix to log messages.
    • Examples: TimeStampedLogger and PrefixedLogger structs.
  • Client:
    • Definition: The client is responsible for configuring and utilizing the logger objects to log messages in the application.

Lets first create the program for SimpleLogger. So this would be the concrete component adhering to component interface Logger which would be later decorated to create concrete decorators TimeStampedLogger and PrefixedLogger

package main

import "fmt"

// The Decorator Design Pattern in Go is a structural pattern that allows you to dynamically add behavior to an object without affecting the behavior of other objects from the same type

// Component Interface
type Logger interface {
log(message string)
}

// Concrete Component
type SimpleLogger struct{}

func (s *SimpleLogger) log(message string) {
fmt.Printf("Log: %s\n", message)
}

// Client using logger
func main() {
var logger Logger = &SimpleLogger{}
logger.log("Message")
}

Output:

Log: Message

Here the concrete component SimpleLogger adheres to the component interface Logger. Now lets decorate SimpleLogger to create concrete decorators TimeStampedLogger and PrefixedLogger .

package main

import (
"fmt"
"time"
)

// The Decorator Design Pattern in Go is a structural pattern that allows you to dynamically add behavior to an object without affecting the behavior of other objects from the same type

// Component Interface
type Logger interface {
log(message string)
}

// Concrete Component
type SimpleLogger struct{}

func (s *SimpleLogger) log(message string) {
fmt.Printf("Log: %s\n", message)
}

// Decorator
// LoggerDecorator maintains a reference to a Logger object and implements the Logger interface.
type LoggerDecorator struct {
Logger
}

// Concrete Decorators

// TimeStamped concrete decorator
type TimeStampedLogger struct {
LoggerDecorator
}

func (l *TimeStampedLogger) log(message string) {
timeUTC := time.Now().UTC()
timestamp := fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", timeUTC.Year(), timeUTC.Month(), timeUTC.Day(), timeUTC.Hour(), timeUTC.Minute(), timeUTC.Second(), timeUTC.Nanosecond()/1000000)
l.LoggerDecorator.log(fmt.Sprintf("[%s] %s", timestamp, message))
}

// Prefixed concrete decorator
type PrefixedLogger struct {
LoggerDecorator
Prefix string
}

func (p *PrefixedLogger) log(message string) {
p.LoggerDecorator.log(fmt.Sprintf("[%s] %s", p.Prefix, message))
}

// Client using logger
func main() {
var simpleLogger Logger = &SimpleLogger{}
simpleLogger.log("Message")

timeStampedLogger := &TimeStampedLogger{LoggerDecorator{Logger: simpleLogger}}
timeStampedLogger.log("Message")

prefixedLogger := &PrefixedLogger{LoggerDecorator: LoggerDecorator{Logger: timeStampedLogger}, Prefix: "Debug"}
prefixedLogger.log("Message")

}

Output:

Log: Message
Log: [2024-06-09T10:53:23.156Z] Message
Log: [2024-06-09T10:53:23.156Z] [Debug] Message

Conclusion:

As we wrap up our discussion on the Decorator design pattern in Go, it becomes clear that this pattern allows dynamic addition of behavior to objects without altering their source code, adhering to the Open/Closed Principle. It promotes clean, maintainable code through composability and separation of concerns. By using interfaces and struct embedding, it enables modular and reusable extensions, ideal for scalable applications.

Links:

For code example, please refer my Github repository: Design-Patterns-In-Go

This is a great video from Anthony GG for understanding decorator pattern with examples: link


0

0

0

Clint Mathews
Keep it clean and simple!

More Articles

Showwcase is a professional tech network with over 0 users from over 150 countries. We assist tech professionals in showcasing their unique skills through dedicated profiles and connect them with top global companies for career opportunities.

© Copyright 2025. Showcase Creators Inc. All rights reserved.