Codementor Events

Design Patterns in Go: Prototype

Published Apr 03, 2023

The prototype-pattern is a creational design pattern that allows us to create new objects by cloning existing ones, i.e. the existing objects function as a kind of template. This can save time in some cases when for example an object creation involves some heavy calculation or things like network traffic or database queries.

So what does it look like? Well, it is actually quite simple:

prototype.drawio.png

A short explanation:

  1. The main part is the Prototype interface. This defines a Clone method which return a Prototype as well. We will see in the implementation why this is important.
  2. There are some concrete classes which implement the Clone method. In this example there are two, but this could off course be any number.
  3. Finally there is the Client, the class which needs the concrete classes.

Open your terminal or commandline in an empty directory and type:

mkdir go_prototype
cd go_prototype
go mod init github.com/proxy_pattern

Now open your favourite IDE in this directory, and add a main.go file. In that file first type:

package main

import "fmt"

Now we can define the Prototype interface:

type Prototype interface {
  Clone() Prototype
}

In this example we have one ConcretePrototype:

type ConcretePrototype struct {
  name string
}

func (p *ConcretePrototype) Clone() Prototype {
  return &ConcretePrototype{
    name: p.name,
  }
}

Some explanation:

  1. The ConcretePrototype has only one field, a name with type string
  2. The Clone method just instatiates a new ConcretePrototype with a memberwise copy of the fields.

Now let the theory become practice:

func main() {
  prototype := &ConcretePrototype{name: "a name"}

  clone := prototype.Clone().(*ConcretePrototype)

  clone.name = "new name"

  fmt.Println("Original prototype name:", prototype.name)
  fmt.Println("Modified prototype name:", clone.name)
}

A short description

  1. We construct a ConcretePrototype
  2. We clone this prototype. The .(*ConcretePrototype) is a typecast
  3. We change the name on the clone
  4. Then we check that we cloned correctly.

Apparently the Clone method can be written like this, which is much shorter:

func (p *ConcretePrototype) Clone() Prototype {
  newPrototype := *p
  return &newPrototype
}

This seems to be counterintuitive, yet it works, and this is why

  1. The := operator means both a declaration and assignment. That means that newPrototype is at a different memorylocation than p.
  2. The *p denotes the underlying value of p, not its address.
  3. Hence when we return the newPrototype variable, we have an independent clone of the original.

I chose the more verbose version because it is clearer what happens.

As you can see this is an easy pattern to implement. It is especially useful if instantiating is expensive in terms of resources.

Discover and read more posts from Iede Snoek
get started