Simplifying Design Patterns : Prototype

Introduction Design patterns are proven solutions to common software design problems. In this article, we’ll simplify the Prototype pattern—explaining how it works, when to use it, and how to implement it with clear examples. The examples are inspired by Refactoring Guru and Dive Into Design Patterns (Alexander Shvets), distilled to their essence. How Does the Prototype Pattern Work? The Prototype is a creational design pattern that allows you to create copies of existing objects without making your code dependent on their concrete classes. This is useful when object creation is costly or complex (e.g., when objects have private fields or require deep copying). Example Scenario: A Restaurant Kitchen Imagine a restaurant where: You have a Pasta object that you want to duplicate (e.g., for a new order). Some of its properties might be private or require complex initialization (e.g., ingredients, cooking time). Instead of manually reconstructing the object (which could introduce errors or tight coupling), you: Define a Prototype interface with a method like clone(). Make the Pasta class implement this interface, providing its own concrete cloning logic (e.g., shallow or deep copy). Now, the kitchen (client code) can simply call pasta.clone() to get an identical copy without knowing the internal details of the Pasta class. When to Use the Prototype? Use this pattern when: Your code is not supposed to depend on concrete classes of objects you need to copy You want to reduce the amount of subclasses that only differ in the way they are initialized How to Implement the Prototype Pattern in C# Step-by-Step (Restaurant Example) 1. Define the Cloning Interface or Method Option A: Create an IPrototype interface. public interface IPrototype { T Clone(); } Option B: Add Clone() directly to an existing hierarchy. public abstract class Dish { public abstract Dish Clone(); } 2. Implement the Copying Logic Option A: Use a copy constructor. public class Pasta : IPrototype { public string Type { get; set; } public List Ingredients { get; set; } // Copy constructor public Pasta(Pasta source) { this.Type = source.Type; this.Ingredients = new List(source.Ingredients); // Deep copy } public Pasta Clone() { return new Pasta(this); // Invokes copy constructor } } Option B: Manual field copying (e.g., for languages without copy constructors). public class Pasta : IPrototype { public string Type { get; set; } public List Ingredients { get; set; } public Pasta Clone() { return new Pasta() { Type = this.Type, Ingredients = new List(this.Ingredients) // Deep copy }; } } 3. Implement the Clone() Method Ensure the method returns the concrete type (avoid polymorphism issues). // Correct (returns SpicyPasta): public new SpicyPasta Clone() { return new SpicyPasta(this); } Avoid this (risky downcasting): // Warning: Returns Pasta when cast to IPrototype public override Pasta Clone() { return new SpicyPasta(this); } Factory Method vs. Prototype in C# Factory Method: public abstract class Kitchen { public abstract Dish PrepareDish(); } public class PastaKitchen : Kitchen { public override Dish PrepareDish() { return new Pasta(); // Creates from scratch } } Prototype: Pasta prototype = new Pasta { Type = "Carbonara" }; Pasta order1 = prototype.Clone(); // Copies existing object Diagram

Apr 11, 2025 - 21:57
 0
Simplifying Design Patterns : Prototype

Introduction

Design patterns are proven solutions to common software design problems. In this article, we’ll simplify the Prototype pattern—explaining how it works, when to use it, and how to implement it with clear examples. The examples are inspired by Refactoring Guru and Dive Into Design Patterns (Alexander Shvets), distilled to their essence.

How Does the Prototype Pattern Work?

The Prototype is a creational design pattern that allows you to create copies of existing objects without making your code dependent on their concrete classes. This is useful when object creation is costly or complex (e.g., when objects have private fields or require deep copying).

Example Scenario: A Restaurant Kitchen

Imagine a restaurant where:

  • You have a Pasta object that you want to duplicate (e.g., for a new order).
  • Some of its properties might be private or require complex initialization (e.g., ingredients, cooking time).
  • Instead of manually reconstructing the object (which could introduce errors or tight coupling), you:
    • Define a Prototype interface with a method like clone().
    • Make the Pasta class implement this interface, providing its own concrete cloning logic (e.g., shallow or deep copy).
  • Now, the kitchen (client code) can simply call pasta.clone() to get an identical copy without knowing the internal details of the Pasta class.

When to Use the Prototype?

Use this pattern when:

  1. Your code is not supposed to depend on concrete classes of objects you need to copy
  2. You want to reduce the amount of subclasses that only differ in the way they are initialized

How to Implement the Prototype Pattern in C#

Step-by-Step (Restaurant Example)

1. Define the Cloning Interface or Method

  • Option A: Create an IPrototype interface.
  public interface IPrototype<T> {
      T Clone();
  }
  • Option B: Add Clone() directly to an existing hierarchy.
  public abstract class Dish {
      public abstract Dish Clone();
  }

2. Implement the Copying Logic

  • Option A: Use a copy constructor.
  public class Pasta : IPrototype<Pasta> {
      public string Type { get; set; }
      public List<string> Ingredients { get; set; }

      // Copy constructor
      public Pasta(Pasta source) {
          this.Type = source.Type;
          this.Ingredients = new List<string>(source.Ingredients); // Deep copy
      }

      public Pasta Clone() {
          return new Pasta(this); // Invokes copy constructor
      }
  }
  • Option B: Manual field copying (e.g., for languages without copy constructors).
  public class Pasta : IPrototype<Pasta> {
      public string Type { get; set; }
      public List<string> Ingredients { get; set; }

      public Pasta Clone() {
          return new Pasta() {
              Type = this.Type,
              Ingredients = new List<string>(this.Ingredients) // Deep copy
          };
      }
  }

3. Implement the Clone() Method

  • Ensure the method returns the concrete type (avoid polymorphism issues).
  // Correct (returns SpicyPasta):
  public new SpicyPasta Clone() {
      return new SpicyPasta(this);
  }
  • Avoid this (risky downcasting):
  // Warning: Returns Pasta when cast to IPrototype
  public override Pasta Clone() {
      return new SpicyPasta(this);
  }

Factory Method vs. Prototype in C#

  • Factory Method:
  public abstract class Kitchen {
      public abstract Dish PrepareDish();
  }

  public class PastaKitchen : Kitchen {
      public override Dish PrepareDish() {
          return new Pasta(); // Creates from scratch
      }
  }
  • Prototype:
  Pasta prototype = new Pasta { Type = "Carbonara" };
  Pasta order1 = prototype.Clone(); // Copies existing object

Diagram

Prototype Diagram