Implementing Object Construction Strategies: Builder and Prototype Patterns

The Builder Pattern

The Builder pattern is a creational design pattern aimed at constructing complex objects step by step. It enables the creation process to be independent of the representation, allowing the same construction logic to produce different results. Unlike simple factories that provide an object directly, the Builder patttern focuses on assembling components.

Consider a scenario where a catering service needs to generate specific meal plans based on client preferences. A MealPlan product consists of main courses and beverages. Different configurations exist, such as a morning set or a lunch set. The construction logic for these sets remains similar, but the specific items vary.

Implementation

First, define the product component:

class MealPlan {
    private String mainDish;
    private String beverage;

    public void setMainDish(String dish) {
        this.mainDish = dish;
    }

    public void setBeverage(String drink) {
        this.beverage = drink;
    }

    public void display() {
        System.out.println("Plan: " + mainDish + ", Drink: " + beverage);
    }
}

Next, establish the interface for builders:

interface PlanBuilder {
    void prepareMain();
    void prepareDrink();
    MealPlan getResult();
}

Concrete builders implement this interface to define specific assembly steps:

class MorningPlan implements PlanBuilder {
    private MealPlan plan = new MealPlan();

    @Override
    public void prepareMain() {
        plan.setMainDish("Pancake Set");
    }

    @Override
    public void prepareDrink() {
        plan.setBeverage("Soy Milk");
    }

    @Override
    public MealPlan getResult() {
        return plan;
    }
}
class MiddayPlan implements PlanBuilder {
    private MealPlan plan = new MealPlan();

    @Override
    public void prepareMain() {
        plan.setMainDish("Boxed Lunch");
    }

    @Override
    public void prepareDrink() {
        plan.setBeverage("Orange Juice");
    }

    @Override
    public MealPlan getResult() {
        return plan;
    }
}

The Director (KitchenCoordinator) orchestrates the construction process without knowing the specific products being created:

class KitchenCoordinator {
    public void construct(PlanBuilder builder) {
        builder.prepareDrink();
        builder.prepareMain();
        builder.getResult().display();
    }
}

Client usage demonstrates how different plans are generated via the same workflow:

public class MainApplication {
    public static void main(String[] args) {
        KitchenCoordinator coordinator = new KitchenCoordinator();
        
        coordinator.construct(new MorningPlan());
        coordinator.construct(new MiddayPlan());
    }
}

Key Roles:

  1. Builder: Defines abstract interfcaes for creating parts of the product.
  2. ConcreteBuilder: Constructs the actual implementation of the product parts.
  3. Director: Manages the construction sequence.
  4. Product: The resulting complex object.

When to Use:

  • When creating complex objects with many optional parts.
  • When construction algorithms need to remain separate from the representation.

Trade-offs:

  • Pros: Allows independent extensions for concrete builders; isolates construction details.
  • Cons: Increases complexity if there is only one type of product; requires knowledge of all internal parts.

The Prototype Pattern

The Prototype pattern creates duplicate objects efficiently by cloning existing instances instead of using expensive initialization processes. This is particularly useful when creating new objects involves significant resource consumption.

For instance, consider a notification system where messages share identical content but differ only in recipient identifiers. Instead of initializing a message object repeatedly, a prototype can be created once and cloned with minor adjustments.

Implementation

The target class must implement the Cloneable interface to support copying:

class Notification implements Cloneable {
    private String subject;
    private String content;
    private String recipientName;

    public void setContent(String text) {
        this.content = text;
    }

    public void setName(String name) {
        this.recipientName = name;
    }

    @Override
    public Notification clone() throws CloneNotSupportedException {
        return (Notification) super.clone();
    }

    @Override
    public String toString() {
        return String.format("To: %s | Msg: %s", recipientName, content);
    }
}

Usage example:

public class CloneDemo {
    public static void main(String[] args) {
        Notification template = new Notification();
        template.setContent("Happy Birthday!");

        try {
            Notification userA = template.clone();
            Notification userB = template.clone();

            userA.setName("Xiao Ming");
            userB.setName("Xiao Hong");

            System.out.println(userA.toString());
            System.out.println(userB.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

This approach relies on shallow cloning by default.

Cloning Types:

  1. Shallow Copy: Copies primitive fields and references to objects. Changes to nested objects affect both original and clone. Achieved by overriding clone() and calling super.clone().
  2. Deep Copy: Recursively copies all nested objects. Primitive fields and object graphs are duplicated. Requires implementing serialization or manually copying reference fields.

When to Use:

  • Avoiding expensive database queries or heavy computation during initialization.
  • Managing multiple variants of a configuration object.
  • Dynamic environments requiring runtime object duplication.

Trade-offs:

  • Pros: Performance improvement by skipping instantiation overhead.
  • Cons: Cloning complex structures manually is error-prone; debugging copy interactions can be difficult.

Tags: java Design Patterns Creational Patterns Builder Pattern Prototype Pattern

Posted on Fri, 15 May 2026 06:19:19 +0000 by Redapple