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:
- 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.
- There are some concrete classes which implement the Clone() method. In this example there are two, but this could of course be any number.
- 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 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:
- The ConcretePrototype has only one field, a name with type string
- 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
- We construct a ConcretePrototype
- We clone this prototype. The .(*ConcretePrototype) is a typecast
- We change the name on the clone
- Then we check that we cloned correctly.
Alternative version of the Clone method
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
- The := operator means both a declaration and assignment. That means that newPrototype is at a different memorylocation than p.
- The *p denotes the underlying value of p, not its address.
- 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.
Conclusion
As you can see this is an easy pattern to implement. This pattern is especially useful if instantiating is expensive in terms of resources.