In my first internship, one of the tasks I had to do was learned about Go as well as revised on design patterns. So I mixed them together by trying to come up with my own examples for the patterns and implement them in Go.

I picked out those that I thought were being used more often instead of doing it for every single patterns mentioned in the famous book about design patterns by the Gang of Four(GoF).

Table of Contents

Creational Patterns

Builder

Definition

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations. - GoF

In layman’s terms, when working with complex objects in OOP, we try to delegate the creation of objects to a different object called a Builder.

The Builder object is an independent object that create the final product we want through steps we designed it to.

Another way to understand is by imagining it as an assembly line where we provide a detail model of what we want and the machine will build each part and assemble them together. All of which is done behind the scenes and we only care about the model design we input and the final output product.

Implementation

The Builder Pattern comprises of 4 main components:

Those are the theories however, in real applications that I saw, mostly there’s only a Product and a Builder class.

Example code written in Go: Full sourcecode

Here we will try to implement Builder Pattern to create “product” which are computer objects with different models(Mac, Windows) with data: “name”, “price”, and “description”.

type Product struct {
    name Name
    price Price
    description Description
}

Name and Description are of type string, and price is an integer. Then we define an abstract builder.

type AbstractBuilder interface {
    Build() Product
}

Next up are Concrete Builders that implements the AbstractBuilder interface.

type WindowsBuilder struct{}

func (compBuilder WindowsBuilder) Build() Product {
	computer := Product{
		name:        "Windows",
		price:       100,
		description: "This model was built to run on Windows"}
	return computer
}

type MacBuilder struct{}

func (macBuilder MacBuilder) Build() Product {
	computer := Product{
		name:        "OS X",
		price:       200,
		description: "This model was built to run on OS X"}
	return computer
}

And finally the Director that make use of builders.

type Director struct {
    builder AbstractBuilder
}

//newBuilder : users provide the Director which builder they want to use
func (director *Director) newBuilder(concreteBuilder AbstractBuilder) {
    director.builder = concreteBuilder
}

//makeComputer : an interface for users to build product they want
//without needing to care about how it is built
func (director *Director) makeComputer() Product {
    return director.builder.Build()
}

Conclusion

The builder pattern is better used when the representations of different complex objects do not share a common interface. It provides a few notable advantages over other methods of creation:

However there are a few disadvantages as well:

Factory Method

Definition

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. - GoF

In layman terms, we want to have our program create objects without knowing which type of object until runtime.

Let’s say we have a text editor with the function OpenDocument. And in our code we have some subclasses that implement ways to represent different types of documents. However, which type of document we need to use is not known until the user has chosen a file. This is a case where the factory method can be extremely helpful because you can avoid numerous duplications of IF-ElSE and SWITCH-CASE statements.

Implementation

Factory Method consists of 4 participants:

Full sourcecode

First of are the Product interface and implementations of ConcreteProduct

type Document interface {
    Show()
    New() Document
}

type XMLDocument struct {
    xmlData string
}
func (xmlDoc XMLDocument) Show() {
    fmt.Println(xmlDoc.xmlData)
}
func (xmlDoc XMLDocument) New() Document {
    newDoc := XMLDocument{xmlData: "New XML doc"}
    return newDoc
}

type JSONDocument struct {
    jsonData string
}


func (jsonDoc JSONDocument) Show() {
    fmt.Println(jsonDoc.jsonData)
}
func (jsonDoc JSONDocument) New() Document {
    newDoc := JSONDocument{jsonData: "New JSON doc"}
    return newDoc
}

Then we create a Creator interface (IApplication) that declares the factory method and the ConcreteCreator(CoreApplication) that implements the factory method. Also, we must provide the factory with the models for creation.

type IApllication interface {
    OpenFile(fileType string) Document
}

type CoreApplication struct {
    DocFactory map[string]Document
}
func (coreApp CoreApplication) OpenFile(fileType string) Document {
    return coreApp.DocFactory[fileType].New()
}


func (coreApp *CoreApplication) AddDocType(typeName string, doc Document) {
    if coreApp.DocFactory != nil {
        coreApp.DocFactory[typeName] = doc
    } else {
        coreApp.DocFactory = make(map[string]Document)
        coreApp.DocFactory[typeName] = doc
    }
}

And in main

func main() {
    fmt.Println("Factory Method Demo")
    var coreApp CoreApplication
    coreApp.AddDocType("XML", XMLDocument{})
    coreApp.AddDocType("JSON", JSONDocument{})
    newDoc := coreApp.OpenFile("XML")
    newDoc.Show()
    newDoc = coreApp.OpenFile("JSON")
    newDoc.Show()
}

Conclusion

This pattern is generally useful when objects don’t care about how it’s created or we don’t want to repeatedly hardcode object creation branches. Some biggest disadvantages of the pattern involve tight coupling between different layers of abstraction and overall longer code, more classes.

Structural Patterns

Adapter

Definition

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces. - GoF

When programming, incompatibility issues are prone to occur. There will be times when we need to reuse code of different interfaces. To do that we create an adapter subclass that provides us with the interface we need, but also able to process and reuse code from a different interface.

Implementation

Main components of Adapter Pattern:

Let say we have a Car interface that allows us to call Run(). We have a NormalCar that can run and a RaceCar that can run. However there exists a different kind of automobile we call FlyingCar that can drive on the ground or fly in the sky. Now we will try to create an adapter called AdvancedCar to make use of FlyingCar while maintaining the same interface as Car.

Full sourcecode

First are some definitions of Car, NormalCar and RaceCar:

type Car interface {
    Run()
}

type NormalCar struct {
    Name string
}

func (car NormalCar) Run() {
    fmt.Println("Normal car is running")
}

type RaceCar struct {
    Name string
}

func (car RaceCar) Run() {
    fmt.Println("Race car is speeding")
}

Then a new type of automobile is introduced called FlyingCar:

type FlyingCar struct {
    Name string
}

func (car FlyingCar) Drive() {
    fmt.Println("Flying car is driving")
}

func (car FlyingCar) Fly() {
    fmt.Println("Flying car is flying")
}

But since our client is using the interface for Car we need to adapt this FlyingCar through an adapter class called AdvancedCar:

type AdvancedCar struct {
    flyCar FlyingCar
    Name   string
}


func (advCar AdvancedCar) Run() {
    advCar.flyCar.Drive()
}

type Client struct {
    car Car
}

We have done all the work behind the scenes, our client only has to call Run() on the car he chooses.

type Client struct {
    car Car
}

func (client Client) makeCarRun() {
    client.car.Run()
}

In main program:

func main() {
    fmt.Println("Adapter Pattern Demo")
    client := Client{car: NormalCar{Name: "Normal Car"}}
    client.makeCarRun()
    client.car = RaceCar{Name: "Race Car"}
    client.makeCarRun()
    client.car = AdvancedCar{Name: "Advanced Car"}
    client.makeCarRun()
}

Conclusion

Adapter Pattern is used a lot in Android development for handling lists, list views and can be applied the same way to other platform development. It is useful when we need an existing unrelated class to help us with a task but want the interface to match our specific context. However, we must be careful when determining how much “adapting” it does or should it be a two-way adapter.

Decorator

Definition

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. - GoF

So basically we use this pattern when we want to add additional behaviors or states to objects without applying inheritance because it causes too much dependency and unnecessary coupling.

To achieve that we will create a Decorator, which forwards requests to its Component object. It may optionally perform additional operations before and after forwarding the request.

Implementation

Key parts of Decorator Pattern include:

Here is an example of a Pizza class and 2 Decorator class that add behaviors or state to the pizza without needing inheritance.

Full sourcecode

We have an interface for Pizza type with Describe function that will print a string and default getter and setter.

type Pizza interface {
    Describe(Pz Pizza)
    SetName(name string)
    GetName() string
}

Then two types of pizza that implements the Pizza interface: The original Italian pizza

type ItalianPizza struct {
    Name string
}

func (pz *ItalianPizza) Describe(Pz Pizza) {
    fmt.Printf("My pizza type: %s\n", pz.Name)
}

And a different kind of pizza only available in Vietnam:

type VietnamesePizza struct {
    Name    string
    EggType string
}

func (pz *VietnamesePizza) Describe(Pz Pizza) {
    fmt.Printf("Different pizza type: %s with a different member: %s\n",
        pz.Name,
        pz.EggType)
}

Then we create an interface Decorator:

type Decorator interface {
    Decorate(func(pz Pizza)) func(pz Pizza)
}

In this example we will have 2 types of concrete decorator implements Decorator, one to extend a behavior:

type Decorator interface {
    Decorate(func(pz Pizza)) func(pz Pizza)
}

type BehaviorDecorator struct{}

func (d BehaviorDecorator) Decorate(f func(pz Pizza)) func(pz Pizza) {
    return func(pz Pizza) {
        f(pz)
        pz.SetName("Behavior Name")
        fmt.Printf("I changed pizza's name to: %s\n", pz.GetName())
    }
}

And another for an extend in member:

type StateDecorator struct {
    hot string
}

func (stateDecorator StateDecorator) Decorate(f func(pz Pizza)) func(pz Pizza) {
    return func(pz Pizza) {
        f(pz)
        fmt.Printf("Added hot string: %s to %s\n", stateDecorator.hot, pz.GetName())
    }
}

By applying closure functions in Go, we can use these decorators as follows:

func main() {
    fmt.Println("Decorator Pattern Demo")
    pizza := ItalianPizza{Name: "Original Itatalian Name"}
    newBehavior := BehaviorDecorator{}
    newState := StateDecorator{hot: "so hot"}
    decorator := newBehavior.Decorate(newState.Decorate(pizza.Describe))
    decorator(&pizza)
    vietPizza := VietnamesePizza{Name: "Banh trang nuong", EggType: "Trung cut"}
    decorator = newState.Decorate(vietPizza.Describe)
    decorator(&vietPizza)
}

Conclusion

Decorator Pattern provides more flexibility than static inheritance. In addition it helps avoid creating a high-level class that adds responsibilities. Decorators are easily created, customized and add on as needed. However, it also means we need to create many small similar decorator classes, which can be hard for someone else to understand from the start.

Behavioral Patterns

Observer

Definition

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. - GoF

In other words, Observer Pattern helps us handle events by having the abstract observer to notify dependending subjects of such events so those subjects could make changes accordingly. Most importantly this is achieved without having the subjects tightly coupled.

Implementation

Main structure of Observer Pattern:

Below we will try to implement 2 observers, one will observe the string Name and the other observe the integer Price. Full sourcecode

First of is the Observer interface and our ConcreteObservers:

type Observer interface {
    Update(*ConcreteSubject)
}

type NameObserver struct {
    Name string
}
func (nameObs *NameObserver) Update(subject *ConcreteSubject) {
    fmt.Printf("Updated Name from: %s to %s\n", nameObs.Name, subject.Name)
    nameObs.Name = subject.Name
}
type PriceObserver struct {
    Price int
}
func (priceObs *PriceObserver) Update(subject *ConcreteSubject) {
    fmt.Printf("Updated Price from: %d to %d\n", priceObs.Price, subject.Price)
    priceObs.Price = subject.Price
}

Then we define and implement the Subject and ConcreteSubject:

type Subject interface {
    Notify()
    Attach(Observer)
    Detach(Observer)
}

// An event
type ConcreteSubject struct {
    Name         string
    Price        int
    ObserversMap map[Observer]struct{}
}

func (subject *ConcreteSubject) makeChanges(name string, price int) {
    subject.Name = name
    subject.Price = price
}

func (subject *ConcreteSubject) Attach(obs Observer) {
    subject.ObserversMap[obs] = struct{}{}
}

func (subject *ConcreteSubject) Detach(obs Observer) {
    delete(subject.ObserversMap, obs)
}

func (subject ConcreteSubject) Notify() {
    for obs := range subject.ObserversMap {
        obs.Update(&subject)
    }
}

As you can see the ConcreteSubject maintains a list of Observers and will notify all of them when needed. In our main program, these types can be used like so:

func main() {
    fmt.Println("Observer Pattern Demo")
    newEvent := ConcreteSubject{Name: "Orgin",
        Price:        1,
        ObserversMap: map[Observer]struct{}{}}
    newNameObserver := NameObserver{Name: "Blank"}
    newPriceObserver := PriceObserver{Price: 0}
    newEvent.Attach(&newNameObserver)
    newEvent.Attach(&newPriceObserver)
    newEvent.Notify()
    newEvent.makeChanges("new name", 100)
    newEvent.Notify()
    newEvent.makeChanges("another name", -1)
    newEvent.Notify()
}

//OUTPUT:
//Observer Pattern Demo
//Updated Name from: Blank to Orgin
//Updated Price from: 0 to 1
//Updated Name from: Orgin to new name
//Updated Price from: 1 to 100
//Updated Name from: new name to another
//Updated Price from: 100 to -1

Conclusion

Despite the advantage of making code more reusable, Observer Pattern has a few problems such as: client might forget to call Notify, an observer might want to listen to many subjects at once or unproper deletion of subjects leaves nil references.

Strategy

Definition

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. - GoF

One thing can usually be done multiple ways, however having separate classes for each algorithm makes it harder to maintain. So we can have one object and use Strategy Pattern to specify the encapsulated algorithms at runtime depending on the context.So when we need to change the algorithms of a certain object at runtime based on some specific context, we can consider Strategy Pattern as a solution.

Implementation

These are the main components of Strategy Pattern:

Let’s say we want an object to have a sort function. However we have different sorting algorithms based on some contexts. Full sourcecode

Assume we will have context with formatted array structure provided at run time and depending that we will execute the sort algorithm that can provide the fastest run time for that particular array

type Context struct {
    strategy Strategy
}

func (context Context) Sort() {
    context.strategy.DoSort()
}

For the strategy of each sort algorithm:

type Strategy interface {
    DoSort()
}
type MergeSort struct{}

func (msort MergeSort) DoSort() {
    fmt.Println("Merge sort algo is being used")
}

type BubbleSort struct{}

func (bsort BubbleSort) DoSort() {
    fmt.Println("Bubble sort is being used")
}

type QuickSort struct{}

func (qsort QuickSort) DoSort() {
    fmt.Println("Quick sort is being used")
}

Then we can run them like this:

func main() {
    fmt.Println("Strategy Pattern Demo")
    context := Context{strategy: QuickSort{}}
    context.Sort()
    context.strategy = MergeSort{}
    context.Sort()
    context.strategy = BubbleSort{}
    context.Sort()
}

Conclusion

Strategy Pattern helps avoid conditional statements by delegating the task to the strategy. However, clients must be aware of these strategies to use them appropriately and it increases the number of objects which can be hard to maintain if not implemented properly. Strategy Pattern lets you change the guts of an object while Decorator pattern lets you change the skin.

Architectural Patterns

MVC

Definition

Model View Controller (MVC) is a software architecture pattern, commonly used to implement user interfaces: it is therefore a popular choice for architecting web apps. However it can also be applied for the whole software as well. MVC tries to separate the application into 3 components that are loosely coupled so it can be extended easily.

Let’s create a simplified, minimalistic version of an MVC software. Sourcecode

First is the view:

type View interface {
    Show()
    SetContent(string)
}

type TextBox struct {
    content string
}

func (txtBox TextBox) Show() {
    fmt.Println("Text Box content: ", txtBox.content)
}

func (txtBox *TextBox) SetContent(content string) {
    txtBox.content = content
}

Then a model to store and operate on data:

type Model interface {
    Set(int)
    Get() int
}

type Database struct {
    data int
}

func (db *Database) Set(value int) {
    db.data = value
}

func (db Database) Get() int {
    return db.data
}

And last but not least the controller, usually in real web apps, the controller and the view will interact via some sort of event handling delegate.

type Controller interface {
    Clicked(View, Model)
}

type TextController struct{}

func (txtController TextController) Clicked(view View, model Model) {
    fmt.Println("A button was clicked")
    model.Set(30)
    value := model.Get()
    content := strconv.Itoa(value)
    view.SetContent(content)
    view.Show()
}

In our main(), they will interact with each other like this:

webView := TextBox{}
dataModel := Database{}
defaultController := TextController{}
defaultController.Clicked(&webView, &dataModel)

Conclusion

The MVC pattern can be implemented differently depending on usage but at the core, it serves to speed up the development process as well as maintainability and scalability. Because the 3 components are separated and loosely coupled they can be developed simultaneously. For the same reason, a Model can be used by different sets of controllers. Controllers can control different views with the same interface making the code extremely flexible.

References