Design Pattern: Decorator Pattern in Go

Decorator pattern add new functionality to an existing implementation by adding a new layer. The responsibility/functionality is added to individual objects.

This article demonstrates Decorator pattern implementations in Go. Check the following examples.

Implementation

Here are the steps for Decorator pattern implementation-

Pre-existing implementations-

  • Create an interface for the item/subject class.
  • Create subject/item class and implement the interface.

Decorator implementation steps-

  • Create new struct for the decorator.
  • Create decorator struct and implement the decorator interface.
  • Declare a field for storing a reference to the subject/item class object. Set the subject field while creating a new decorator object.
  • In the interface implementation (or any other method) use/call methods from the subject, according to requirement.

Here is a simple decorator implementation-

// Simple Decorator pattern implementaion in GoLang

package main

import "fmt"

// Subject
type Subject interface {
	OperationOne()
	OperationTwo()
}

// Subject struct
type ConcreteSubject struct {
}

func NewConcreteSubject() (concreteSubject *ConcreteSubject) {
	concreteSubject = &ConcreteSubject{}
	return
}

func (concreteSubject *ConcreteSubject) OperationOne() {
	fmt.Println("Performing Operation One(1) in Subject")
}

func (concreteSubject *ConcreteSubject) OperationTwo() {
	fmt.Println("Performing Operation Two(2) in Subject")
}

// Decorator
type Decorator interface {
	DecoratorOperationOne()
	DecoratorOperationTwo()
}

// Concrete decorator
type ConcreteDecorator struct {
	subject Subject
}

func NewConcreteDecorator(subject Subject) (concreteDecorator *ConcreteDecorator) {
	concreteDecorator = &ConcreteDecorator{}
	concreteDecorator.subject = subject
	return
}

func (concreteDecorator *ConcreteDecorator) DecoratorOperationOne() {
        // Some additional operations here

	concreteDecorator.subject.OperationOne()

        // Some additional operations here
}

func (concreteDecorator *ConcreteDecorator) DecoratorOperationTwo() {
        // Some additional operations here

	concreteDecorator.subject.OperationTwo()

        // Some additional operations here
}

// Demo
func main() {
	someDecorator := NewConcreteDecorator(NewConcreteSubject())
	someDecorator.DecoratorOperationOne()
}

This code will generate the following output-

Performing Operation One(1) in Subject

Examples

Here are a few examples-

Example #1: Data Export

In this example we have a simple data exporter. We want to add new functionality to the data exporter.

Here we are implementing 3 decorators, that adds layer on top of the simple exporter.

DataExport Interface [Existing]

  • Create file “date_export.go”.
  • Define interface “DataExport”.
  • Declare methods according to the requirement. Here we have – “ProcessData”.
// data_export.go

package main

type DataExport interface {
	ProcessData()
}

SimpleExport Struct [Existing]

  • Create file “simple_date_export.go”.
  • Define interface “SimpleDataExport”.
  • Create method “NewSimpleDataExport” for creating a new “SimpleDataExport” object.
  • Implement interface “DataExport” for struct “SimpleDataExport”.
// simple_data_export.go

package main

import "fmt"

type SimpleDataExport struct {
}

func NewSimpleDataExport() (simpleDataExport *SimpleDataExport) {
	simpleDataExport = &SimpleDataExport{}
	return
}

func (simpleDataExport *SimpleDataExport) ProcessData() {
	fmt.Println("SimpleDataExport: Processing Data")
}

Main Decorator Struct [Decorator]

  • Create file “data_export_decorator.go”.
  • Define struct “DataExportDecorator”.
  • Define a field “dataExport” of type “DataExport”. This will be used to store a reference to “DataExport” object.
  • Create method “NewDataExportDecorator” for creating a new “DataExportDecorator” object.
// data_export_decorator.go

package main

type DataExportDecorator struct {
	dataExport DataExport
}

func NewDataExportDecorator(dataExport DataExport) (dataExportDecorator *DataExportDecorator) {
	dataExportDecorator = &DataExportDecorator{}
	dataExportDecorator.dataExport = dataExport
	return
}

func (dataExportDecorator *DataExportDecorator) ProcessData() {
	dataExportDecorator.dataExport.ProcessData()
}

CSV Decorator Struct [Decorator]

  • Create file “csv_data_export_decorator.go”.
  • Define struct “CsvDataExportDecorator”.
  • Define/embed “DataExprtDecorator” into the struct.
  • Create method “NewCsvDataExportDecorator” for creating a new “CsvDataExportDecorator” object. 
  • Declare method “processCsv” for the struct.
  • Define method “ProcessData” for the struct. Use “processCsv” of this struct, and “ProcessData” from the decorator, in the method.
// csv_data_export_decorator.go

package main

import "fmt"

type CsvDataExportDecorator struct {
	*DataExportDecorator
}

func NewCsvDataExportDecorator(dataExporter DataExport) (csvDataExportDecorator *CsvDataExportDecorator) {
	csvDataExportDecorator = &CsvDataExportDecorator{}
	csvDataExportDecorator.DataExportDecorator = NewDataExportDecorator(dataExporter)
	return
}

func (csvDataExportDecorator *CsvDataExportDecorator) processCsv() {
	fmt.Println("Processed data to CSV")
}

func (csvDataExportDecorator *CsvDataExportDecorator) ProcessData() {
	csvDataExportDecorator.DataExportDecorator.ProcessData()
	csvDataExportDecorator.processCsv()
}

Excel Decorator Struct [Decorator]

  • Create file “excel_data_export_decorator.go”.
  • Define struct “ExcelDataExportDecorator”.
  • Define/embed “DataExportDecorator” into the struct.
  • Create method “NewExcelDataExportDecorator” for creating a new “ExcelDataExportDecorator” object. 
  • Declare method “processExcel” for the struct.
  • Define method “ProcessData” for the struct. Use “processExcel” of this struct, and “ProcessData” from the decorator, in the method.
// excel_data_export_decorator.go

package main

import "fmt"

type ExcelDataExportDecorator struct {
	*DataExportDecorator
}

func NewExcelDataExportDecorator(dataExporter DataExport) (excelDataExportDecorator *ExcelDataExportDecorator) {
	excelDataExportDecorator = &ExcelDataExportDecorator{}
	excelDataExportDecorator.DataExportDecorator = NewDataExportDecorator(dataExporter)
	return
}

func (excelDataExportDecorator *ExcelDataExportDecorator) ProcessData() {
	excelDataExportDecorator.DataExportDecorator.ProcessData()
	excelDataExportDecorator.processExcel()
}

func (excelDataExportDecorator *ExcelDataExportDecorator) processExcel() {
	fmt.Println("Processed data to Excel")
}

JSON Decorator Struct [Decorator]

  • Create file “json_data_export_decorator.go”.
  • Define struct “JsonDataExportDecorator”.
  • Define/embed “DataExportDecorator” into the struct.
  • Create method “NewJsonDataExportDecorator” for creating a new “JsonDataExportDecorator” object. 
  • Declare method “processJson” for the struct.
  • Define method “ProcessData” for the struct. Use “processJson” of this struct, and “ProcessData” from the decorator, in the method.
// json_data_export_decorator.go

package main

import "fmt"

type JsonDataExportDecorator struct {
	*DataExportDecorator
}

func NewJsonDataExportDecorator(dataExporter DataExport) (jsonDataExportDecorator *JsonDataExportDecorator) {
	jsonDataExportDecorator = &JsonDataExportDecorator{}
	jsonDataExportDecorator.DataExportDecorator = NewDataExportDecorator(dataExporter)
	return
}

func (jsonDataExportDecorator *JsonDataExportDecorator) ProcessData() {
	jsonDataExportDecorator.DataExportDecorator.ProcessData()
	jsonDataExportDecorator.processJson()
}

func (jsonDataExportDecorator *JsonDataExportDecorator) processJson() {
	fmt.Println("Processed data to JSON")
}

Demo

Now we can pass an object of “SimpleDataExport” to decorator, while creating a new decorator object (Csv/Json/Excel Decorator).

// main.go

package main

func main() {
	csvDataExport := NewCsvDataExportDecorator(NewSimpleDataExport())
	csvDataExport.ProcessData()

	excelDataExport := NewExcelDataExportDecorator(NewSimpleDataExport())
	excelDataExport.ProcessData()

	jsonDataExport := NewJsonDataExportDecorator(NewSimpleDataExport())
	jsonDataExport.ProcessData()
}

Output

The output of the demo above will be like below.

SimpleDataExport: Processing Data
Processed data to CSV

SimpleDataExport: Processing Data
Processed data to Excel

SimpleDataExport: Processing Data
Processed data to JSON

Example #2: UI Elements

In this example we have some UI elements like Buttons, Input files, Tables, etc. We want to decorate these elements with border and/or background and/or margin.

We can do this without any change in the UI element implementation, by using Decorator pattern.

UI Element Interface

  • Create file “ui_element.go”.
  • Define interface “UIElement”.
  • Declare method “Draw” for the interface.
// ui_element.go

package main

type UIElement interface {
	Draw()
}

Button UI Element Struct

  • Create file “button.go”.
  • Define struct “Button”.
  • Define method “NewButton” which is responsible for creating a new “Button” object.
  • Define method “Draw” for the struct, as part of “UIElement” interface implementation.
// button.go

package main

import "fmt"

type Button struct {
}

func NewButton() (button *Button) {
	button = &Button{}
	return
}

func (button *Button) Draw() {
	fmt.Println("Drawing Button")
}

Input Box UI Element Struct

  • Create file “input_box.go”.
  • Define the struct “InputBox”.
  • Define method “NewInputBox” which is responsible for creating a new “InputBox” object.
  • Implement interface “UIElement” for the struct. Define the method “Draw” for the struct, as part of the implementation.
// input_box.go

package main

import "fmt"

type InputBox struct {
}

func NewInputBox() (inputBox *InputBox) {
	inputBox = &InputBox{}
	return
}

func (inputBox *InputBox) Draw() {
	fmt.Println("Drawing Input Box")
}

Table UI Element Struct

  • Create file “table.go”.
  • Define the struct “Table”.
  • Define method “NewTable” which is responsible for creating a new “Table” object.
  • Implement interface “UIElement” for the struct. Define the method “Draw” for the struct, as part of the “UIElement” interface implementation.
// table.go

package main

import "fmt"

type Table struct {
}

func NewTable() (table *Table) {
	table = &Table{}
	return
}

func (table *Table) Draw() {
	fmt.Println("Drawing Table")
}

UI Decorator [Decorator]

  • Create file “ui_decorator.go”.
  • Define the struct “UIDecorator”.
  • Define field “uiElement” of type “UIElement” to store a reference of “UIElement”.
  • Define method “NewUIDecorator” for creating a new “UIDecorator” object.
  • Define method “Draw” and use/call the “Draw” method from “uiElement” in the method implementation.
// ui_decorator.go

package main

type UIDecorator struct {
	uiElement UIElement
}

func NewUIDecorator(uiElement UIElement) (uiDecorator *UIDecorator) {
	uiDecorator = &UIDecorator{}
	uiDecorator.uiElement = uiElement
	return
}

func (uiDecorator *UIDecorator) Draw() {
	uiDecorator.uiElement.Draw()
}

Border Decorator Struct [Decorator]

  • Create file “border_decorator.go”.
  • Define the struct “BorderDecorator”.
  • Embed “UIDecorator” into this struct.
  • Define method “NewBorderDecorator” for creating a new “BorderDecorator” object.
  • Define method “Draw” and use/call the “Draw” method from “uiElement”. Add the implementation for adding border, in the method.
// border_decorator.go

package main

import "fmt"

type BorderDecorator struct {
	*UIDecorator
}

func NewBorderDecorator(uiElement UIElement) (borderDecorator *BorderDecorator) {
	borderDecorator = &BorderDecorator{}
	borderDecorator.UIDecorator = NewUIDecorator(uiElement)
	return
}

func (borderDecorator *BorderDecorator) Draw() {
	// Can perform any additional task anywhere in the method

	borderDecorator.UIDecorator.Draw()

	// Write code to add border to the element
	fmt.Println("Adding Border to the element")
}

Background Decorator Struct [Decorator]

  • Create file “background_decorator.go”.
  • Define the struct “BackgroundDecorator”.
  • Embed “UIDecorator” into this struct.
  • Define method “NewBackgroundDecorator” for creating a new “BackgroundDecorator” object.
  • Implement method “Draw” and call the “Draw” method from “uiElement” for drawing the element. Add functionality for adding background, in the method.
// background_decorator.go

package main

import "fmt"

type BackgroundDecorator struct {
	*UIDecorator
}

func NewBackgroundDecorator(uiElement UIElement) (backgroundDecorator *BackgroundDecorator) {
	backgroundDecorator = &BackgroundDecorator{}
	backgroundDecorator.UIDecorator = NewUIDecorator(uiElement)
	return
}

func (backgroundDecorator *BackgroundDecorator) Draw() {
	// Can perform any additional task anywhere in the method

	backgroundDecorator.UIDecorator.Draw()

	// Write code to add background to the element
	fmt.Println("Adding Background to the element")
}

Margin Decorator Struct [Decorator]

  • Create file “margin_decorator.go”.
  • Define the struct “MarginDecorator”.
  • Embed “UIDecorator” into this struct.
  • Define method “NewMarginDecorator”, which creates a new “MarginDecorator” object.
  • Define method “Draw” and use/call the “Draw” method from “uiElement”. Add margin in the method.
// margin_decorator.go

package main

import "fmt"

type MarginDecorator struct {
	*UIDecorator
}

func NewMarginDecorator(uiElement UIElement) (marginDecorator *MarginDecorator) {
	marginDecorator = &MarginDecorator{}
	marginDecorator.UIDecorator = NewUIDecorator(uiElement)
	return
}

func (marginDecorator *MarginDecorator) Draw() {
	// Can perform any additional task anywhere in the method

	marginDecorator.UIDecorator.Draw()

	// Write code to add margin to the element
	fmt.Println("Adding margin to the element")
}

Demo

The following code demonstrates how to use multiple decorators on a single UI Element.

We can pass the UI object to the decorator, to add additional functionality. We can also chain the call and use multiple decorators for an element.

// main.go

package main

func main() {
	tableWithBorder := NewBorderDecorator(NewTable())
	tableWithBorder.Draw()

	inputWithBorderAndBackground := NewBackgroundDecorator(NewBorderDecorator(NewInputBox()))
	inputWithBorderAndBackground.Draw()

	buttonWithAllDecorator := NewMarginDecorator(NewBackgroundDecorator(NewBorderDecorator(NewButton())))
	buttonWithAllDecorator.Draw()
}

Output

The following output will be generated-

Drawing Table
Adding Border to the element


Drawing Input Box
Adding Border to the element
Adding Background to the element


Drawing Button
Adding Border to the element
Adding Background to the element
Adding margin to the element

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Decorator pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.