Prototype Pattern #

Imagine a game system managing hundreds of NPCs (Non-Player Characters). Every Soldier NPC has the same base stats — health 100, attack 15, armor 10 — plus equipment already loaded from a database, animations already parsed from files, and an AI behavior tree already compiled. Creating each new soldier from scratch means repeating all those expensive steps for every instance. A far more efficient approach: make one “template” soldier, then have each new soldier clone the template and modify only the values that need to differ — name, position, or carried items. That is the essence of the Prototype Pattern: creating new objects by duplicating an existing one, rather than building it from scratch every time.

What Is the Prototype Pattern? #

The Prototype Pattern is a creational design pattern that creates new objects by cloning an existing object (called the prototype), instead of calling a constructor and filling every field from zero. The cloned object acts as a mold — it holds ready-made initial state, and each clone can modify specific parts without affecting the original prototype.

In Go, there is no built-in clone mechanism like in Java or C++. The Prototype Pattern is implemented explicitly through an interface with a Clone() method, and every struct is responsible for implementing its own clone correctly — including handling reference-type fields like maps, slices, and pointers.

Two properties define the Prototype Pattern:

  • Cloning produces an independent object — modifications to the clone must not affect the prototype, and vice versa
  • The client does not need to know the concrete type — it only needs to know the object can be cloned through the interface
flowchart LR
    subgraph "Without Prototype"
        direction TB
        C1[Create NPC 1\\nfrom scratch] --> D1["Load DB\\nParse animations\\nCompile AI\\n⏱ ~200ms"]
        C2[Create NPC 2\\nfrom scratch] --> D2["Load DB\\nParse animations\\nCompile AI\\n⏱ ~200ms"]
        C3[Create NPC 3\\nfrom scratch] --> D3["Load DB\\nParse animations\\nCompile AI\\n⏱ ~200ms"]
    end

    subgraph "With Prototype"
        direction TB
        T[Template NPC] --> D4["Load DB\\nParse animations\\nCompile AI\\n⏱ ~200ms once"]
        D4 --> P[Prototype ready]
        P -->|Clone + modify| N1["NPC 1 ⏱ ~1ms"]
        P -->|Clone + modify| N2["NPC 2 ⏱ ~1ms"]
        P -->|Clone + modify| N3["NPC 3 ⏱ ~1ms"]
    end

When Is Prototype Needed #

Prototype is not a pattern for every situation. There are three concrete conditions that make Prototype the right choice.

Condition 1: Expensive Object Creation #

If creating the object involves time-consuming or resource-heavy operations — database queries, file reads, network calls, parsing large XML/JSON, or intensive upfront computation — cloning is far more efficient than rebuilding from scratch.

// ANTI-PATTERN: every request creates a new ReportTemplate from scratch
func GenerateReport(reportType string, data []Row) (*Report, error) {
    template := &ReportTemplate{}
    template.LoadStylesheet("corporate.css")        // read file
    template.LoadLogoAsset("logo.png")              // read file
    template.ParseLayout("quarterly_layout.xml")    // parse XML
    template.InitChartEngine()                      // initialize engine
    // total: ~150ms for every report

    return template.Render(data)
}

// CORRECT: the template is prepared once, each report clones it
var reportPrototype *ReportTemplate

func init() {
    reportPrototype = &ReportTemplate{}
    reportPrototype.LoadStylesheet("corporate.css")
    reportPrototype.LoadLogoAsset("logo.png")
    reportPrototype.ParseLayout("quarterly_layout.xml")
    reportPrototype.InitChartEngine()
    // total: ~150ms once at startup
}

func GenerateReport(reportType string, data []Row) (*Report, error) {
    template := reportPrototype.Clone() // ~1ms — just a field copy
    return template.Render(data)
}

Condition 2: Many Objects with Similar Structure #

When many objects share the same base state and only differ in a few fields, Prototype is cleaner than creating many subclasses or repeatedly calling a constructor with many parameters.

Condition 3: Object Creation at Runtime #

When the type of object to create is determined at runtime — for example, based on user configuration or incoming data — a Prototype Registry enables object creation without hardcoding concrete types in client code.


Shallow Copy vs Deep Copy #

This is the most critical aspect of the Prototype Pattern. A mistake in the clone implementation is the source of bugs that are very hard to detect, because objects look different while still sharing the same data in memory.

Shallow Copy: Sharing References #

A shallow copy copies field values directly. For primitive fields (int, string, bool, float64) this is safe because the values get copied. But for reference-type fields (map, slice, pointer, interface), only the memory address is copied — not the data.

type Config struct {
    Name    string
    Timeout int
    Headers map[string]string // reference type — DANGEROUS for shallow copy
    Tags    []string          // reference type — DANGEROUS for shallow copy
}

// ANTI-PATTERN: a shallow copy incorrectly called "clone"
func (c *Config) ShallowClone() *Config {
    copy := *c // copy all fields directly
    return &copy
    // copy.Headers and c.Headers point to the SAME MAP in memory!
    // copy.Tags and c.Tags point to the SAME SLICE in memory!
}

func demonstrateShallowCopyDanger() {
    original := &Config{
        Name:    "payment-service",
        Timeout: 30,
        Headers: map[string]string{"Auth": "Bearer token-original"},
        Tags:    []string{"production"},
    }

    clone := original.ShallowClone()
    clone.Name = "refund-service"         // SAFE — string is a value type
    clone.Timeout = 60                    // SAFE — int is a value type
    clone.Headers["Auth"] = "Bearer token-clone" // DANGEROUS — modifies the original too!
    clone.Tags = append(clone.Tags, "staging")   // DANGEROUS — depends on slice capacity

    fmt.Println(original.Headers["Auth"]) // "Bearer token-clone" — not "Bearer token-original"!
    // A bug that is extremely hard to trace
}

Deep Copy: Every Object Independent #

A deep copy creates a fresh copy of every reference type. The resulting clone is truly independent — no shared state between the clone and the prototype.

// CORRECT: a safe deep copy
func (c *Config) Clone() *Config {
    // Copy primitive fields via value copy
    clone := &Config{
        Name:    c.Name,
        Timeout: c.Timeout,
    }

    // Deep copy for maps — create a new map, copy every entry
    clone.Headers = make(map[string]string, len(c.Headers))
    for k, v := range c.Headers {
        clone.Headers[k] = v
    }

    // Deep copy for slices — create a new slice, copy every element
    clone.Tags = make([]string, len(c.Tags))
    copy(clone.Tags, c.Tags)

    return clone
}

func demonstrateDeepCopySafety() {
    original := &Config{
        Name:    "payment-service",
        Timeout: 30,
        Headers: map[string]string{"Auth": "Bearer token-original"},
        Tags:    []string{"production"},
    }

    clone := original.Clone()
    clone.Headers["Auth"] = "Bearer token-clone"
    clone.Tags = append(clone.Tags, "staging")

    fmt.Println(original.Headers["Auth"]) // "Bearer token-original" — unaffected
    fmt.Println(original.Tags)            // ["production"] — unaffected
}

Nested Objects: Recursive Deep Copy #

For structs that contain other structs (nested objects), the deep copy must be done recursively.

type Address struct {
    Street string
    City   string
    ZIP    string
}

type Customer struct {
    Name    string
    Email   string
    Address *Address       // pointer — needs deep copy
    Orders  []*Order       // slice of pointers — needs deep copy
    Metadata map[string]interface{} // map with interface{} — needs special care
}

func (c *Customer) Clone() *Customer {
    clone := &Customer{
        Name:  c.Name,
        Email: c.Email,
    }

    // Deep copy the pointer to Address
    if c.Address != nil {
        addressCopy := *c.Address // struct value copy — safe because all fields are strings
        clone.Address = &addressCopy
    }

    // Deep copy the slice of pointers
    if c.Orders != nil {
        clone.Orders = make([]*Order, len(c.Orders))
        for i, order := range c.Orders {
            clone.Orders[i] = order.Clone() // every Order must have its own Clone() too
        }
    }

    // Deep copy the map
    if c.Metadata != nil {
        clone.Metadata = make(map[string]interface{}, len(c.Metadata))
        for k, v := range c.Metadata {
            clone.Metadata[k] = v // be careful if a value is a reference type
        }
    }

    return clone
}
flowchart TD
    subgraph "Shallow Copy"
        direction LR
        O1[Original] -->|share| M1[map in memory]
        C1[Clone] -->|share| M1
        style M1 fill:#ff9999
    end

    subgraph "Deep Copy"
        direction LR
        O2[Original] --> M2[original map]
        C2[Clone] --> M3[new map — a copy]
        style M2 fill:#99ff99
        style M3 fill:#99ff99
    end

Shallow Copy on Reference Types Is a Hidden Bug

Bugs from shallow copy usually do not show up in unit tests, because tests tend to use one instance at a time. The bug appears in production when two goroutines modify objects they believe are independent at the same time, causing an intermittent race condition that is extremely hard to reproduce.


Full Implementation: Game Entity System #

Let’s build a realistic Prototype implementation — an entity system for a game that manages various character types with cloneable stats and equipment.

The Prototype Interface #

package entity

// Cloneable is the Prototype interface — every game entity must implement it.
type Cloneable interface {
    Clone() Cloneable
}

// Stats stores an entity's numeric attributes.
type Stats struct {
    Health    int
    MaxHealth int
    Attack    int
    Defense   int
    Speed     int
    Level     int
}

// Clone creates an independent copy of Stats.
func (s Stats) Clone() Stats {
    return s // a struct without pointer/slice/map — value copy is already safe
}

// Equipment stores the items an entity is wearing.
type Equipment struct {
    Weapon string
    Armor  string
    Helmet string
    Ring   string
}

// Clone creates an independent copy of Equipment.
func (e Equipment) Clone() Equipment {
    return e // a struct without pointer/slice/map — value copy is already safe
}

Concrete Prototype: GameEntity #

// GameEntity represents a character in the game.
type GameEntity struct {
    ID          string
    Name        string
    Type        string
    Stats       Stats
    Equipment   Equipment
    Skills      []string          // slice — needs deep copy
    Buffs       map[string]int    // map — needs deep copy
    AIBehavior  string
    SpawnWeight float64
}

// Clone produces a new GameEntity independent of the prototype.
// Any modification to the clone does not affect the original entity.
func (e *GameEntity) Clone() Cloneable {
    clone := &GameEntity{
        ID:          generateID(),   // a new ID for every clone
        Name:        e.Name,
        Type:        e.Type,
        Stats:       e.Stats.Clone(),
        Equipment:   e.Equipment.Clone(),
        AIBehavior:  e.AIBehavior,
        SpawnWeight: e.SpawnWeight,
    }

    // Deep copy Skills
    if e.Skills != nil {
        clone.Skills = make([]string, len(e.Skills))
        copy(clone.Skills, e.Skills)
    }

    // Deep copy Buffs
    if e.Buffs != nil {
        clone.Buffs = make(map[string]int, len(e.Buffs))
        for k, v := range e.Buffs {
            clone.Buffs[k] = v
        }
    }

    return clone
}

// AsEntity type-asserts a Cloneable into a *GameEntity.
func AsEntity(c Cloneable) *GameEntity {
    if e, ok := c.(*GameEntity); ok {
        return e
    }
    return nil
}

func generateID() string {
    return fmt.Sprintf("entity-%d", time.Now().UnixNano())
}

Usage: Clone and Modify #

func main() {
    // Create a soldier prototype — the expensive process runs once
    soldierPrototype := &entity.GameEntity{
        Name: "Soldier",
        Type: "humanoid",
        Stats: entity.Stats{
            Health:    100,
            MaxHealth: 100,
            Attack:    15,
            Defense:   10,
            Speed:     5,
            Level:     1,
        },
        Equipment: entity.Equipment{
            Weapon: "Iron Sword",
            Armor:  "Leather Armor",
        },
        Skills:     []string{"BasicAttack", "Block"},
        Buffs:      map[string]int{},
        AIBehavior: "aggressive",
        SpawnWeight: 0.6,
    }

    // Clone for various soldier variants — almost free
    regularSoldier := entity.AsEntity(soldierPrototype.Clone())
    regularSoldier.Name = "Regular Soldier"

    eliteSoldier := entity.AsEntity(soldierPrototype.Clone())
    eliteSoldier.Name = "Elite Soldier"
    eliteSoldier.Stats.Health = 150
    eliteSoldier.Stats.Attack = 25
    eliteSoldier.Equipment.Weapon = "Steel Sword"
    eliteSoldier.Equipment.Armor = "Chain Mail"
    eliteSoldier.Skills = append(eliteSoldier.Skills, "PowerStrike")

    captainSoldier := entity.AsEntity(soldierPrototype.Clone())
    captainSoldier.Name = "Captain"
    captainSoldier.Stats.Level = 5
    captainSoldier.Stats.Health = 200
    captainSoldier.Buffs["leadership_aura"] = 1

    // The prototype is unaffected by clone modifications
    fmt.Println(soldierPrototype.Stats.Health) // 100 — unchanged
    fmt.Println(soldierPrototype.Skills)       // [BasicAttack Block] — not extended
    fmt.Println(soldierPrototype.Equipment.Weapon) // Iron Sword — unchanged
}

Prototype Registry #

In complex systems, prototypes are not held directly in variables — they are stored in a registry that can be accessed from anywhere and supports registering new prototypes at runtime.

package entity

import (
    "fmt"
    "sync"
)

// Registry stores a collection of prototypes that can be cloned at any time.
// Thread-safe for use from multiple goroutines.
type Registry struct {
    mu         sync.RWMutex
    prototypes map[string]Cloneable
}

// NewRegistry creates a new, empty Registry.
func NewRegistry() *Registry {
    return &Registry{
        prototypes: make(map[string]Cloneable),
    }
}

// Register adds a prototype under a given name.
// Overwrites an existing prototype if the name is the same.
func (r *Registry) Register(name string, prototype Cloneable) {
    r.mu.Lock()
    defer r.mu.Unlock()
    r.prototypes[name] = prototype
}

// Create returns a clone of the registered prototype.
// Returns an error if the prototype name is not found.
func (r *Registry) Create(name string) (Cloneable, error) {
    r.mu.RLock()
    defer r.mu.RUnlock()

    prototype, ok := r.prototypes[name]
    if !ok {
        return nil, fmt.Errorf("prototype %q not found in registry", name)
    }
    return prototype.Clone(), nil
}

// List returns the names of all registered prototypes.
func (r *Registry) List() []string {
    r.mu.RLock()
    defer r.mu.RUnlock()

    names := make([]string, 0, len(r.prototypes))
    for name := range r.prototypes {
        names = append(names, name)
    }
    return names
}

// Unregister removes a prototype from the registry.
func (r *Registry) Unregister(name string) {
    r.mu.Lock()
    defer r.mu.Unlock()
    delete(r.prototypes, name)
}

Using the Registry in a real application:

func setupEntityRegistry() *entity.Registry {
    registry := entity.NewRegistry()

    // Register all prototypes at startup
    registry.Register("soldier", &entity.GameEntity{
        Name:       "Soldier",
        Type:       "humanoid",
        Stats:      entity.Stats{Health: 100, Attack: 15, Defense: 10},
        Skills:     []string{"BasicAttack", "Block"},
        AIBehavior: "aggressive",
    })

    registry.Register("archer", &entity.GameEntity{
        Name:       "Archer",
        Type:       "humanoid",
        Stats:      entity.Stats{Health: 75, Attack: 20, Defense: 5, Speed: 8},
        Skills:     []string{"ArrowShot", "QuickShot", "Retreat"},
        AIBehavior: "ranged",
    })

    registry.Register("mage", &entity.GameEntity{
        Name:       "Mage",
        Type:       "humanoid",
        Stats:      entity.Stats{Health: 60, Attack: 30, Defense: 3, Speed: 4},
        Skills:     []string{"Fireball", "IceSpike", "Teleport"},
        AIBehavior: "caster",
    })

    registry.Register("boss_dragon", &entity.GameEntity{
        Name:        "Ancient Dragon",
        Type:        "dragon",
        Stats:       entity.Stats{Health: 5000, Attack: 80, Defense: 50, Level: 30},
        Skills:      []string{"DragonBreath", "TailSwipe", "FlyingRampage", "AncientRoar"},
        AIBehavior:  "boss",
        SpawnWeight: 0.01,
    })

    return registry
}

// Spawn creates a new entity instance from the registry.
// The only way to create entities across the whole game — no hardcoded types here.
func Spawn(registry *entity.Registry, entityType string) (*entity.GameEntity, error) {
    cloned, err := registry.Create(entityType)
    if err != nil {
        return nil, fmt.Errorf("failed to spawn entity %q: %w", entityType, err)
    }
    return entity.AsEntity(cloned), nil
}

func main() {
    registry := setupEntityRegistry()

    // Spawn various entities from the registry — no concrete types here
    for i := 0; i < 10; i++ {
        soldier, _ := Spawn(registry, "soldier")
        soldier.Name = fmt.Sprintf("Soldier-%d", i+1)
    }

    // Plugins can register new entities into the registry at runtime
    registry.Register("custom_mob", loadCustomMobFromConfig("config/custom_mob.yaml"))
}
sequenceDiagram
    participant Game as Game Loop
    participant Reg as Registry
    participant Proto as Prototype
    participant Clone as New Entity

    Game->>Reg: Create("soldier")
    Reg->>Proto: Clone()
    Proto->>Clone: deep copy all fields
    Clone-->>Reg: new instance
    Reg-->>Game: *GameEntity (independent)
    Game->>Clone: Modify name, position, etc.
    Note over Proto: Prototype is unaffected

Combinations with Other Patterns #

Prototype rarely stands alone — it is often combined with other patterns for stronger solutions.

Prototype + Factory Method #

Factory Method can use Prototype as its internal mechanism. Instead of creating new objects, the factory clones a pre-configured prototype.

// EntityFactory uses an internal prototype registry
type EntityFactory struct {
    registry *entity.Registry
}

func NewEntityFactory(registry *entity.Registry) *EntityFactory {
    return &EntityFactory{registry: registry}
}

// CreateEntity is a factory method that uses cloning behind the scenes
func (f *EntityFactory) CreateEntity(entityType string) (*entity.GameEntity, error) {
    return Spawn(f.registry, entityType)
}

// CreateElite creates an elite version of an entity — clone + modify
func (f *EntityFactory) CreateElite(entityType string) (*entity.GameEntity, error) {
    base, err := Spawn(f.registry, entityType)
    if err != nil {
        return nil, err
    }
    base.Name = "Elite " + base.Name
    base.Stats.Health = int(float64(base.Stats.Health) * 1.5)
    base.Stats.Attack = int(float64(base.Stats.Attack) * 1.3)
    return base, nil
}

Prototype + Builder #

A Builder can use a prototype as the base object, then modify specific fields through its fluent API.

// EntityBuilder uses a prototype as its starting point
type EntityBuilder struct {
    entity *entity.GameEntity
}

func NewEntityBuilderFrom(prototype *entity.GameEntity) *EntityBuilder {
    return &EntityBuilder{
        entity: entity.AsEntity(prototype.Clone()), // clone first, modify later
    }
}

func (b *EntityBuilder) WithName(name string) *EntityBuilder {
    b.entity.Name = name
    return b
}

func (b *EntityBuilder) WithLevel(level int) *EntityBuilder {
    b.entity.Stats.Level = level
    b.entity.Stats.Health = int(float64(b.entity.Stats.MaxHealth) * (1 + float64(level)*0.1))
    b.entity.Stats.Attack = int(float64(b.entity.Stats.Attack) * (1 + float64(level)*0.05))
    return b
}

func (b *EntityBuilder) WithSkill(skill string) *EntityBuilder {
    b.entity.Skills = append(b.entity.Skills, skill)
    return b
}

func (b *EntityBuilder) Build() *entity.GameEntity {
    return b.entity
}

// Usage: Prototype + Builder for highly customized entities
bossVariant := NewEntityBuilderFrom(soldierPrototype).
    WithName("Veteran Commander").
    WithLevel(10).
    WithSkill("CommandRoar").
    WithSkill("BattleCry").
    Build()

Testing Prototype #

Testing the Prototype Pattern focuses on two things: ensuring the clone produces equal values, and ensuring the clone is truly independent of the prototype.

func TestGameEntity_Clone_ProducesEqualValues(t *testing.T) {
    original := &entity.GameEntity{
        Name: "Soldier",
        Type: "humanoid",
        Stats: entity.Stats{Health: 100, Attack: 15},
        Skills: []string{"BasicAttack", "Block"},
        Buffs:  map[string]int{"rage": 5},
    }

    cloned := entity.AsEntity(original.Clone())

    // Values must be equal
    if cloned.Name != original.Name {
        t.Errorf("Name: got %q, want %q", cloned.Name, original.Name)
    }
    if cloned.Stats.Health != original.Stats.Health {
        t.Errorf("Stats.Health: got %d, want %d", cloned.Stats.Health, original.Stats.Health)
    }
    if !reflect.DeepEqual(cloned.Skills, original.Skills) {
        t.Errorf("Skills differ: got %v, want %v", cloned.Skills, original.Skills)
    }
}

func TestGameEntity_Clone_IsIndependent(t *testing.T) {
    original := &entity.GameEntity{
        Name:   "Soldier",
        Skills: []string{"BasicAttack"},
        Buffs:  map[string]int{"rage": 5},
    }

    cloned := entity.AsEntity(original.Clone())

    // Modifying the clone must not affect the original
    cloned.Name = "Elite Soldier"
    cloned.Skills = append(cloned.Skills, "PowerStrike")
    cloned.Buffs["poison"] = 3

    if original.Name != "Soldier" {
        t.Errorf("original.Name affected: got %q", original.Name)
    }
    if len(original.Skills) != 1 {
        t.Errorf("original.Skills affected: got %v", original.Skills)
    }
    if _, ok := original.Buffs["poison"]; ok {
        t.Errorf("original.Buffs affected — shallow copy bug!")
    }
}

func TestGameEntity_Clone_HasNewID(t *testing.T) {
    original := &entity.GameEntity{Name: "Soldier"}
    clone1 := entity.AsEntity(original.Clone())
    clone2 := entity.AsEntity(original.Clone())

    if clone1.ID == clone2.ID {
        t.Errorf("two clones share the same ID: %s", clone1.ID)
    }
    if clone1.ID == original.ID {
        t.Errorf("clone has the same ID as the original")
    }
}

func TestRegistry_CreateReturnsClone(t *testing.T) {
    registry := entity.NewRegistry()
    prototype := &entity.GameEntity{
        Name:   "Soldier",
        Skills: []string{"BasicAttack"},
    }
    registry.Register("soldier", prototype)

    entity1, err := registry.Create("soldier")
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }

    entity2, _ := registry.Create("soldier")

    // Both must be independent — not the same pointer
    if entity1 == entity2 {
        t.Error("registry returned the same pointer, not a clone")
    }
}

func TestRegistry_CreateUnknown_ReturnsError(t *testing.T) {
    registry := entity.NewRegistry()

    _, err := registry.Create("unknown_type")
    if err == nil {
        t.Error("expected error for unknown prototype, got nil")
    }
}

When to Use and When Not to #

USE Prototype if:
  ✓ Object creation involves expensive operations (I/O, network, parsing)
  ✓ Many objects share the same initial state and only differ in a few fields
  ✓ The type of object to create is determined at runtime, not compile time
  ✓ You want to avoid subclass explosion for every small variation
  ✓ You need object snapshots that can be modified without affecting the original

AVOID Prototype if:
  ✗ Object creation is already cheap and simple — no performance benefit
  ✗ The struct contains circular references — cloning becomes very complex
  ✗ The object holds external resources (connections, file handles) that cannot be cloned
  ✗ All fields are primitives and the constructor is already expressive enough
  ✗ The team is unfamiliar with deep copy — the shallow copy bug risk is too high

Objects with External Resources Are Not Suited to Prototype

If the struct stores resources tied to the OS or network — such as *sql.DB, *os.File, net.Conn, or active channels — cloning the struct does not clone those resources. The clone and the original will share the same resource, which can cause bugs that are extremely hard to debug. For such objects, use the Factory Pattern or an Object Pool.


Prototype Review Checklist #

CLONE IMPLEMENTATION:
  □ All primitive fields are copied correctly (value copy)
  □ All maps are deep copied (create a new map, copy every entry)
  □ All slices are deep copied (create a new slice, copy the elements)
  □ All pointers are deep copied (allocate a new struct, copy its value)
  □ Nested structs with reference types are also cloned recursively
  □ A new unique ID or identifier is generated for every clone

REGISTRY (if used):
  □ Register and Create operations are thread-safe (use sync.RWMutex)
  □ Create returns an error when the name is not found
  □ Prototypes stored in the registry are not modified from outside

TESTING:
  □ Test that the clone produces the same values as the original
  □ Test that modifying the clone does not affect the original
  □ Test that modifying the original does not affect an already-made clone
  □ Test that every clone has a unique ID
  □ Test for race conditions if Clone() is called from multiple goroutines

DOCUMENTATION:
  □ The Clone() method documents whether it is shallow or deep copy
  □ Fields intentionally shared (if any) are clearly explained
  □ External resources that are not cloned are explicitly mentioned

Summary #

  • Prototype clones an existing object — instead of building from scratch; ideal when object construction is expensive or when many objects share the same initial state.
  • Deep copy is a duty, not a choice — shallow copying reference types (map, slice, pointer) creates shared mutable state that becomes a source of extremely hard-to-debug race condition bugs.
  • Every field needs different handling: primitives are copied by value assignment; maps are recreated and their entries copied; slices are recreated and their elements copied; pointers are reallocated.
  • The Prototype Registry enables dynamic registration and creation of prototypes — great for plugin systems, multi-tenant configs, and game entity systems.
  • Powerful combinations: Prototype + Factory Method to encapsulate creation logic; Prototype + Builder for step-by-step customization of a base object.
  • Objects with external resources are not suitable for cloning*sql.DB, net.Conn, and *os.File cannot be meaningfully cloned; use Factory or Object Pool for those cases.
  • Testing must include an independence test — verify that clone modifications do not affect the original; this is the most important test for proving the deep copy works correctly.
  • Don’t use Prototype for simple objects — if the constructor is already cheap and expressive, adding Clone() only adds complexity without real benefit.

← Previous: Builder   Next: Adapter →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact