Design Pattern Series - Prototype (Golang)
Previous post in series Builder Pattern
The Prototype Design Pattern is a creational design pattern that allows new objects to be created by copying an existing object, called a prototype, instead of creating them from scratch. This pattern is especially useful when creating an object that is expensive or complex, and you want to reuse the structure of an existing object.
What is Prototype Design Pattern?
The Prototype Pattern involves creating a prototype object and cloning it to create new objects. Instead of using a constructor or a factory, this pattern relies on a cloning mechanism to reproduce the state of the prototype. It is useful in cases where:
- Creating new objects is resource-intensive (e.g. requires database queries or complex initialization).
- Need to create objects with small differences from the original object.
- Want to avoid using subclasses to create objects.
In Golang, which does not have traditional inheritance, the Prototype Pattern can be implemented using a struct and an interface to achieve similar functionality.
Main components of Prototype Pattern
- Prototype Interface: Defines a method to clone objects.
- Concrete Prototype: Implements the clone method and stores the state to be cloned.
- Client: Uses the prototype to create new objects by cloning.
Implement the Prototype Pattern in Golang
We will create a system for managing document types (e.g., resumes) that can be cloned and customized to demonstrate this pattern:
=> Implement the Prototype Pattern with a resume document.
package main
import (
"fmt"
)
// Prototype interface
type Document interface {
Clone() Document
Display()
}
// Concrete Prototype: Resume
type Resume struct {
Name string
Skills []string
Experience string
}
// Clone method for Resume
func (r *Resume) Clone() Document {
// Deep copy to avoid slice share
skillsCopy := make([]string, len(r.Skills))
copy(skillsCopy, r.Skills)
return &Resume{
Name: r.Name,
Skills: skillsCopy,
Experience: r.Experience,
}
}
// Display method for Resume
func (r *Resume) Display() {
fmt.Printf("Resume: %s\nSkills: %v\nExperience: %s\n\n", r.Name, r.Skills, r.Experience)
}
func main() {
// Create a prototype resume
prototypeResume := &Resume{
Name: "John Doe",
Skills: []string{"Go", "Python", "SQL"},
Experience: "5 years of SE",
}
// Clone prototype to create new resume
resume1 := prototypeResume.Clone()
resume2 := prototypeResume.Clone()
// Edit created resume
resume1.(*Resume).Name = "Alice Smith"
resume1.(*Resume).Skills = append(resume1.(*Resume).Skills, "JavaScript")
resume2.(*Resume).Name = "Bob Johnson"
resume2.(*Resume).Experience = "3 years of Backend Developer"
// Render
fmt.Println("Prototype Resume:")
prototypeResume.Display()
fmt.Println("Cloned Resume 1:")
resume1.Display()
fmt.Println("Cloned Resume 2:")
resume2.Display()
}
Explain:
- Prototype Interface:
- Interface
Documentdefine methodClone()return a newDocumentand methodDisplay()to display the state of the object.
- Interface
- Concrete Prototype (Resume):
- Struct
Resumeimplement interfaceDocument. - Method
Clone()create a copy (deep copy) of the resume, make sure that the slices areSkillsnot shared between prototype and copy. - Method
Display()print details of the resume.
- Struct
- Client (main):
- A prototype resume is created with default values.
- Two new resumes are created by cloning the prototype.
- Each cloned resume can be edited independently without affecting the prototype or other copies.
Output
Prototype Resume:
Resume: John Doe
Skills: [Go Python SQL]
Experience: 5 years of SE
Cloned Resume 1:
Resume: Alice Smith
Skills: [Go Python SQL JavaScript]
Experience: 5 years of SE
Cloned Resume 2:
Resume: Bob Johnson
Skills: [Go Python SQL]
Experience: 3 years of Backend Developer
This shows that the copies are independent of the prototype and of each other.
Benefit of the Prototype Pattern
- Efficiency: Reduce the cost of creating complex objects by reusing existing objects.
- Flexible: Allows dynamic creation of objects with different configurations.
- Simplified object creation: Avoids complex constructors or factories, especially in languages like Go that do not have class inheritance.
- Decoupling: Clients do not need to know the details of object creation.
When to use the Prototype Pattern
- When object instantiation is resource-intensive (e.g., loading data from a database).
- When multiple objects with similar properties but slight differences are needed.
- When you want to avoid using subclasses to create object variants.
- When the system needs to be independent of how objects are created.
Note in Golang
- Deep Copy vs. Shallow Copy: In Go, it is necessary to ensure deep copy for fields like slices or maps to avoid unintended reference sharing. The above example explicitly copies the Skills slice.
- Interface: Go's interfaces are well suited for defining the Clone() method, making this pattern flexible and reusable.
- Concurrency: If using in a concurrent environment, make sure the prototype is thread-safe or immutable to avoid race conditions.
Conclude
Prototype Design Pattern is a powerful tool for efficient object creation in Golang. By leveraging interfaces and careful cloning, you can implement this pattern to simplify object creation and improve performance.
The above example demonstrates cloning a resume, but this pattern can be applied to many different areas, such as configuration management, game development, or any situation where object cloning is needed.
Thanks for reading!