Design Patterns in Go: Prototype

Introduction

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:

A short explanation:

  1. The main part is the Prototype interface. This defines a Clone() method which returns a Prototype. 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 of course be any number.
  3. Finally there is the Client, the class which needs the concrete classes.

Implementation in Go

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[T any] interface {
	Clone() T
}

As you can see this is a generic interface.. We need to do this, to make the code more typesafe as we shall see later on.

To demonstrated deep copying later on, let’s make an Address struct:

type Address struct {
	Street string
	City   string
}

In this example we define and implement a ConcretePrototype:

type ConcretePrototype struct {
	name    string
	age     int
	address *Address
	tags    []string
}

func (c *ConcretePrototype) Clone() *ConcretePrototype {
	newAddress := &Address{
		Street: c.address.Street,
		City:   c.address.City,
	}

	newTags := make([]string, len(c.tags))
	copy(newTags, c.tags)

	return &ConcretePrototype{
		name:    c.name,
		age:     c.age,
		address: newAddress,
		tags:    newTags,
	}
}

Some explanation:

  1. The ConcretePrototype has several fields: name, age, tags which is a string slice, and a field with type Address
  2. The Clone()method does the following:
    • create a new address using the existing values.
    • Create a copy of the tags slice.
    • Finally, create a new ConcretePrototype struct and return it.

Time to test

Now let the theory become practice:

func main() {
	original := &ConcretePrototype{
		name: "Original",
		age:  25,
		address: &Address{
			Street: "123 Main St",
			City:   "Springfield",
		},
		tags: []string{"employee", "developer"},
	}

	// No type assertion needed - Clone() returns *ConcretePrototype
	clone1 := original.Clone()
	clone2 := original.Clone()

	// Modify clones to demonstrate independence
	clone1.name = "Clone 1"
	clone1.address.City = "New York"
	clone1.tags[0] = "manager"

	clone2.name = "Clone 2"
	clone2.address.Street = "456 Oak Ave"

	fmt.Printf("Original: Name=%s, Age=%d, Address=%+v, Tags=%v\n",
		original.name, original.age, *original.address, original.tags)
	fmt.Printf("Clone 1:  Name=%s, Age=%d, Address=%+v, Tags=%v\n",
		clone1.name, clone1.age, *clone1.address, clone1.tags)
	fmt.Printf("Clone 2:  Name=%s, Age=%d, Address=%+v, Tags=%v\n",
		clone2.name, clone2.age, *clone2.address, clone2.tags)
}

A short description

  1. We construct a ConcretePrototype
  2. We clone this prototype and change some fields.
  3. We repeat this.
  4. By printing out we can see that we have cloned correctly and that each clone is independent of one another.

Conclusion

The Prototype Pattern offers a clean and efficient way to create new objects by cloning existing ones, avoiding the cost of complex instantiation logic. As shown in the Go implementation, it helps you make deep copies of objects safely and type-safely, without having to rebuild them from scratch. This pattern shines in situations where object creation is expensive or involves shared configurations. In short, the Prototype Pattern helps keep your code flexible, efficient, and easier to maintain when dealing with object duplication.

The Code Nomad
The Code Nomad
Articles: 165

Leave a Reply

Your email address will not be published. Required fields are marked *