The Prototype Pattern is a creational design pattern that allows for creating new objects by copying existing instances (prototypes) rather than instantiating new ones from scratch. This pattern is particularly useful when the cost of creating an object from scratch is expensive (e.g., due to complex setup or resource-intensive initialization), or when you want to avoid subclasses for every possible configuration of an object.
Key Concepts of the Prototype Pattern
-
Prototype Interface: Defines a
Clone
method to create a copy of the object. This interface is implemented by classes that need to support cloning. -
Concrete Prototype: An object that implements the
Clone
method, allowing it to create a copy of itself. - Client Code: Uses the prototype to clone new instances instead of creating them directly, ensuring flexibility in the types of objects it works with.
Example Use Case in Go
Imagine a scenario where we have different types of shapes (Circle
, Rectangle
) with various properties. Instead of recreating these shapes, we can clone an existing shape and modify only the necessary attributes.
Code Example in Go
package main
import (
"fmt"
)
// Shape defines the prototype interface with a Clone method.
type Shape interface {
Clone() Shape
GetInfo() string
}
// Circle represents a concrete prototype implementing Shape.
type Circle struct {
Radius int
Color string
}
// Clone creates a copy of the Circle with the same properties.
func (c *Circle) Clone() Shape {
return &Circle{Radius: c.Radius, Color: c.Color}
}
// GetInfo provides information about the Circle.
func (c *Circle) GetInfo() string {
return fmt.Sprintf("Circle with radius %d and color %s", c.Radius, c.Color)
}
// Rectangle represents another concrete prototype implementing Shape.
type Rectangle struct {
Width int
Height int
Color string
}
// Clone creates a copy of the Rectangle with the same properties.
func (r *Rectangle) Clone() Shape {
return &Rectangle{Width: r.Width, Height: r.Height, Color: r.Color}
}
// GetInfo provides information about the Rectangle.
func (r *Rectangle) GetInfo() string {
return fmt.Sprintf("Rectangle with width %d, height %d, and color %s", r.Width, r.Height, r.Color)
}
func main() {
// Original shapes
circle := &Circle{Radius: 10, Color: "Red"}
rectangle := &Rectangle{Width: 5, Height: 10, Color: "Blue"}
// Clone shapes
clonedCircle := circle.Clone()
clonedRectangle := rectangle.Clone()
fmt.Println(clonedCircle.GetInfo()) // Output: Circle with radius 10 and color Red
fmt.Println(clonedRectangle.GetInfo()) // Output: Rectangle with width 5, height 10, and color Blue
}
Explanation
-
Prototype Interface (
Shape
): Defines aClone()
method, which each concrete prototype implements to return a copy of itself. -
Concrete Prototypes (
Circle
,Rectangle
): Implement theClone()
method to return copies of their own instances with the same properties. -
Client Code: Calls
Clone()
on existing instances (e.g.,circle.Clone()
) to create new objects without needing to know their exact types, making it flexible and efficient.
Benefits of the Prototype Pattern
- Efficiency: Reduces the cost of creating new instances by cloning existing ones, which is particularly useful for resource-intensive objects.
- Decoupling: Clients are decoupled from specific classes, allowing them to work with prototypes generically.
- Flexibility: Allows for easy creation of customized objects by cloning and tweaking properties on the cloned instance, without affecting the original.
The Prototype Pattern is ideal when object creation is costly or when you need flexibility in creating variations of an object based on an existing template.