Skip to main content

Command Palette

Search for a command to run...

Prototype Design Pattern Simplified

Simplifying Object Cloning with Prototype Patterns in C#

Published
4 min read

The Prototype Pattern is a creational design pattern that allows you to create new objects by copying (cloning) existing objects, rather than creating new instances from scratch.

  • Instead of using new to create an object, you clone an existing object and modify it if needed.

  • It is useful when object creation is costly or complex.

Key idea:

“If creating an object is expensive, make a copy of a prototype instead.”

Why the Prototype Pattern Is Useful

In many software applications, you often need to create objects that are similar but not identical. For example:

  • A game creating multiple enemy characters with shared stats

  • A GUI application generating windows or controls with the same style

  • Business software producing reports, invoices, or contracts that share structure

Instead of repeatedly writing code to build each object from scratch, the Prototype Pattern allows you to:

  • Create a template object once

  • Clone it whenever you need a new instance

  • Modify only the parts that differ

This leads to simpler, cleaner, and more maintainable code.

Example: Enterprise Document Generation

One practical example of the Prototype Pattern is generating enterprise documents like invoices, quotes, or reports.

These documents often share:

  • Headers, footers, and logos

  • Layouts and formatting

  • Calculation logic (totals, taxes)

Yet each document also has unique details like:

  • Customer data

  • Dates and IDs

  • Line items

Using a prototype allows you to define a document template once and then clone it for each new document, only changing the necessary details.

Code Example

1. Line Item (changes per invoice)

public sealed class LineItem
{
    public string Description { get; set; } = "";
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }

    public decimal Total => Quantity * UnitPrice;

    public LineItem Clone() => new()
    {
        Description = this.Description,
        Quantity = this.Quantity,
        UnitPrice = this.UnitPrice
    };
}

public sealed class DocumentHeader
{
    public string CompanyName { get; init; } = "";
    public string Address { get; init; } = "";
    public string LogoPath { get; init; } = "";

    public DocumentHeader Clone() => this with { };
}

public sealed class DocumentFooter
{
    public string LegalText { get; init; } = "";
    public string BankDetails { get; init; } = "";

    public DocumentFooter Clone() => this with { };
}

3. Prototype Interface

public interface IDocumentPrototype<T>
{
    T Clone();
    decimal CalculateTotal();
}

4. Invoice Template (Prototype)

public sealed class InvoiceTemplate : IDocumentPrototype<InvoiceTemplate>
{
    // Shared template structure
    public DocumentHeader Header { get; init; } = new();
    public DocumentFooter Footer { get; init; } = new();
    public decimal TaxRate { get; init; } = 0.20m;

    // Instance-specific data
    public string InvoiceNumber { get; set; } = "";
    public DateTime IssueDate { get; set; } = DateTime.Today;
    public List<LineItem> LineItems { get; private set; } = new();

    public decimal CalculateTotal()
    {
        var subtotal = LineItems.Sum(i => i.Total);
        return subtotal * (1 + TaxRate);
    }

    public InvoiceTemplate Clone()
    {
        return new InvoiceTemplate
        {
            Header = Header.Clone(),
            Footer = Footer.Clone(),
            TaxRate = TaxRate,
            InvoiceNumber = "",
            IssueDate = DateTime.Today,
            LineItems = new List<LineItem>()
        };
    }
}

5. Load Template Once (Factory)

public static class InvoiceTemplateFactory
{
    public static InvoiceTemplate LoadTemplate()
    {
        return new InvoiceTemplate
        {
            Header = new DocumentHeader
            {
                CompanyName = "Acme Corp",
                Address = "123 Business Street",
                LogoPath = "/logos/acme.png"
            },
            Footer = new DocumentFooter
            {
                LegalText = "Payment due within 30 days.",
                BankDetails = "IBAN XX00 0000 0000"
            },
            TaxRate = 0.20m
        };
    }
}

6. Document Service: Create Real Invoices

public sealed class DocumentService
{
    private readonly InvoiceTemplate _invoicePrototype;

    public DocumentService()
    {
        // Load the template once at startup
        _invoicePrototype = InvoiceTemplateFactory.LoadTemplate();
    }

    public InvoiceTemplate CreateInvoice()
    {
        // Clone the template
        var invoice = _invoicePrototype.Clone();

        // Assign unique details
        invoice.InvoiceNumber = $"INV-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N").Substring(0, 6)}";
        invoice.IssueDate = DateTime.Today;

        // Add line items
        invoice.LineItems.Add(new LineItem
        {
            Description = "Website redesign",
            Quantity = 1,
            UnitPrice = 4500m
        });

        invoice.LineItems.Add(new LineItem
        {
            Description = "SEO optimization (6 months)",
            Quantity = 1,
            UnitPrice = 1800m
        });

        return invoice;
    }
}

7. Usage Example

        var documentService = new DocumentService();

        // Create a new invoice
        var invoice = documentService.CreateInvoice();

        Console.WriteLine($"Invoice Number: {invoice.InvoiceNumber}");
        Console.WriteLine($"Issue Date: {invoice.IssueDate:yyyy-MM-dd}");
        Console.WriteLine("Items:");
        foreach (var item in invoice.LineItems)
        {
            Console.WriteLine($"- {item.Description}: {item.Quantity} x {item.UnitPrice:C} = {item.Total:C}");
        }
        Console.WriteLine($"Total (with tax): {invoice.CalculateTotal():C}");

The Prototype Pattern is a simple yet powerful tool for creating new objects by cloning existing ones. It reduces repetitive code, improves performance, and ensures consistency across similar objects.

While this pattern is often used in enterprise scenarios like document generation, its benefits apply broadly—from games and UI components to complex business objects. By defining a template once and cloning it whenever needed, developers can save time, reduce errors, and maintain clean, maintainable code.