The four previous patterns create objects from a class: choosing it (factories), restricting it (Singleton), or assembling it step by step (Builder). Prototype changes the starting point: the new object is not born from a class but from another object, by copying it. It seems like a minor trick until you meet the perfect use case — in PideYa we have it: the "repeat my last order" button and the restaurants' menu templates — and until you discover that copying properly in Java is a minefield with a name of its own: Cloneable. This lesson covers the pattern, its two depths of copying, the problems of Java's standard mechanism, and the alternatives you will actually use in practice.
Contents
- The problem in PideYa: repeating orders and menu templates
- Intent and structure of the pattern
- Shallow vs. deep copying
- Java's standard mechanism:
Cloneableand its problems - The recommended alternative: copy constructors
- Other copying alternatives in Java
- Prototype registry
- When to use it and when not to
- Common mistakes, exercises, and conclusion
The problem in PideYa: repeating orders and menu templates
Case 1 — "Repeat my last order". It is the app's most profitable button: one tap and the customer orders last Friday's meal again. The first implementation rebuilt the order field by field:
// Naive version: copying by hand, field by field. Do NOT imitate.
Order repeated = Order.builder(previous.getCustomer(), previous.getRestaurant())
.homeDelivery(previous.getDeliveryAddress())
.timeSlot(previous.getTimeSlot())
.instructions(previous.getInstructions())
// ...and the lines? and the cutlery? and the contact phone?
.build();The pain: every time Order gains an attribute, someone has to remember to add it here. In sprint 14, contactPhone was added and nobody updated this copy: repeated orders silently lost the phone number. It is the symptom we noted in the module introduction: creating an object "almost identical" to another means rebuilding it entirely, and the manual copy drifts out of sync with the class.
Case 2 — Menu templates. PideYa offers new restaurants starter menus by cuisine type ("pizzeria", "Japanese", "burger joint"), already assembled with typical sections and products, which the restaurant duplicates and customizes. The template menu is a large object (sections → products → options), expensive to assemble from the database; the natural thing is to keep the assembled exemplar and duplicate it for every restaurant that requests it.
Both cases share the same essence: the best "blueprint" for the new object is an object that already exists.
Intent and structure of the pattern
Intent (GoF): specify the kinds of objects to create using a prototypical instance, and create new objects by copying that prototype.
The structure is the simplest in the module: an interface with a single operation, "copy yourself", which each class implements by knowing how to copy itself.
classDiagram
class Copyable~T~ {
<<interface>>
+copy() T
}
class Order {
+copy() Order
}
class Menu {
+copy() Menu
}
class RepeatOrderService {
+repeat(Order) Order
}
Copyable <|.. Order
Copyable <|.. Menu
RepeatOrderService ..> Copyable : requests copies\nwithout knowing the class
| GoF role | Purpose | In PideYa |
|---|---|---|
| Prototype | Interface declaring the copy operation | Copyable<T> with copy() |
| ConcretePrototype | Knows how to copy itself (deciding what and how) | Order, Menu |
| Client | Creates objects by requesting copies, no new, no concrete classes |
RepeatOrderService, restaurant onboarding |
We define the interface with generics so each class returns its own type, with no casts. A note on naming: we deliberately call it Copyable with a copy() method — rather than Prototype/clone() — to avoid clashing both with the pattern's role name and with Java's Object.clone(), whose troubles we will meet shortly:
public interface Copyable<T extends Copyable<T>> {
/** Returns an independent copy of this object. */
T copy();
}Two structural advantages of the pattern before we get into the mud: the client creates objects without knowing their concrete class (a List<Copyable<?>> of templates gets copied uniformly, wherever each one comes from), and the exemplar's state travels for free (the copied "pizzeria" menu already carries its 40 products; with a factory you would have to rebuild them). The devil, as always, is in the verb "copy".
Shallow vs. deep copying
This is THE distinction of this lesson. A shallow copy duplicates the object but shares the referenced objects; a deep copy also duplicates (recursively) whatever it references that is mutable.
classDiagram
direction LR
class OriginalMenu {
name = "Base pizzeria"
}
class ShallowCopiedMenu {
name = "Pizzeria Da Luigi"
}
class SectionList {
THE SAME list
for both menus
}
OriginalMenu --> SectionList
ShallowCopiedMenu --> SectionList : shared danger!
The accident this produces, with our menu template:
Menu template = templates.get("pizzeria");
Menu daLuigiMenu = template.shallowCopy(); // shallow copy
daLuigiMenu.getSections().get(0).addProduct(housePizza);
// Surprise: the "pizzeria" TEMPLATE now also offers Da Luigi's house pizza,
// and so does every restaurant that signs up from today onward.The rule for deciding depth, field by field:
| Field type | Copy needed? |
|---|---|
Primitives (int, boolean...) |
The shallow copy already duplicates them: nothing to do |
References to immutables (String, BigDecimal, LocalDate, immutable records, enums) |
Sharing them is safe and even desirable: nothing to do |
| References to mutables (collections, objects with setters) | Deep copy mandatory if the copy must be independent |
References to deliberately shared entities (the Restaurant, the Customer) |
Sharing is correct: copying here would be a domain error |
Note the last row: depth does not mean "copy everything". When repeating an order, the copy must point to the same customer and restaurant (they are entities of the world, not parts of the order), but it needs its own list of lines. Deciding where to stop copying is design, not mechanics; the composition/aggregation distinction from the UML lesson is exactly the map: what is composed (black diamond) gets copied, what is aggregated (white diamond) gets shared.
Java's standard mechanism: Cloneable and its problems
Java ships with cloning built in: the protected method Object.clone() and the marker interface Cloneable. Here is what it looks like:
public class Menu implements Cloneable {
private String name;
private List<MenuSection> sections = new ArrayList<>();
@Override
public Menu clone() {
try {
Menu copy = (Menu) super.clone(); // shallow copy
// Manual patch-up of the mutable parts to make it deep:
copy.sections = new ArrayList<>();
for (MenuSection s : this.sections) {
copy.sections.add(s.clone()); // requires MenuSection to repeat all this
}
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // impossible: we are Cloneable
}
}
}It works, but the mechanism is universally flagged as one of the language's failed designs (Bloch devotes a devastating chapter to it in Effective Java). Its problems, cataloged:
Cloneabledoes not declareclone(): it is an empty interface that only changes the behavior of aprotectedmethod inherited fromObject. You cannot writeCloneable c = ...; c.clone();— the interface does not give you the method. An invisible contract. (This is why our own interface is calledCopyableand actually declarescopy(): a visible, honest contract.)super.clone()is always shallow: it does a field-by-field binary copy. Everything mutable ends up shared unless you patch it by hand, and forgetting a field is a silent bug (the menu accident).CloneNotSupportedExceptionis checked: it forces the ritualtry/catcheven though it can never fire.- It bypasses constructors: the clone is born without passing through any constructor, dodging invariants, validations, and
finalfields you might want to recompute. A terrible marriage with the immutable classes of the Builder style. - Fragile with inheritance: if a subclass adds mutable fields and forgets to override
clone(), it inherits a broken shallow copy of its own fields.
Honest practical conclusion: know Cloneable so you can read it (it shows up in old code and in the JDK: arrays and ArrayList use it), but do not choose it for new code. The Prototype pattern does not demand Cloneable: it demands a copy operation, and there are better ways to provide one.
The recommended alternative: copy constructors
A copy constructor is a constructor that receives another instance and copies from it. Everything explicit, no ritual exceptions, passing through the constructor (invariants intact), and compatible with final fields:
public final class Order implements Copyable<Order> {
private final Customer customer; // shared: entity
private final Restaurant restaurant; // shared: entity
private final List<OrderLine> lines; // OWN: deep-copy
private final Address deliveryAddress; // immutable: shareable
private final String instructions; // immutable: shareable
// ... remaining fields from the previous lesson ...
/** Copy constructor: the depth policy, explicit and in one place. */
private Order(Order original) {
this.customer = original.customer; // share (aggregation)
this.restaurant = original.restaurant; // share (aggregation)
this.lines = original.lines.stream() // go deep (composition)
.map(OrderLine::new) // <- copy of each line
.collect(Collectors.toCollection(ArrayList::new));
this.deliveryAddress = original.deliveryAddress; // immutable: share
this.instructions = original.instructions;
// ...
}
@Override
public Order copy() {
return new Order(this);
}
}
public class OrderLine {
private final Product product; // menu entity: share
private int quantity; // mutable primitive: travels with the copy
/** The line's copy constructor. */
public OrderLine(OrderLine original) {
this.product = original.product;
this.quantity = original.quantity;
}
// ...
}And the use case shrinks to two lines, immune to future new fields (the copy constructor lives in the class, so whoever adds a field has it right in front of them, making the omission much harder than in an external copy):
public class RepeatOrderService {
public Order repeat(Order previous) {
Order fresh = previous.copy(); // all the state travels: lines, address...
return fresh.withTimeSlot(null) // adjustments for the new context: the slot
.withCreatedAt(LocalDateTime.now()); // and the date are not inherited
}
}(The withX(...) methods — withers — return a copy with that one field changed: Prototype's natural complement on immutable objects. Java records make them trivial.)
Other copying alternatives in Java
- Copy factory methods:
public static Order copyOf(Order o)— identical to the copy constructor with a more expressive name. - Copying via builder: if the class already has a Builder, a
toBuilder()method returning a builder preloaded with the current state marries the best of both patterns:previous.toBuilder().timeSlot(otherSlot).build(). It is the most idiomatic way to "copy with tweaks" in modern Java (Lombok generates it with@Builder(toBuilder = true)). - Records with
withsemantics: for small value objects, an immutable record + withers covers 90% of copying needs with no explicit pattern. - Serialize and deserialize (to JSON or binary): an "automatic" deep copy of the whole graph. Useful in generic test utilities, but slow, fragile with non-serializable fields, and with no control over the share/copy policy: do not use it as a domain mechanism.
Prototype registry
When the prototypes are a managed catalog — exactly our menu templates — the pattern is completed with a registry: a container that maps keys to prototypical exemplars and hands out copies (never the original) to whoever asks:
public class MenuTemplateRegistry {
private final Map<String, Menu> templates = new ConcurrentHashMap<>();
/** Populated at startup... or live, without deploying new code. */
public void register(String key, Menu template) {
templates.put(key, template);
}
/** ALWAYS hands out a copy: the master exemplar stays untouchable. */
public Menu create(String key) {
Menu template = templates.get(key);
if (template == null) {
throw new IllegalArgumentException("No such template: " + key);
}
return template.copy();
}
}
// PideYa startup:
registry.register("pizzeria", basePizzeriaMenu);
registry.register("japanese", baseJapaneseMenu);
// Restaurant onboarding: a big object, assembled and ready, with no class knowledge
Menu menu = registry.create("pizzeria");
menu.setName("Pizzeria Da Luigi");Compare with the factory registry from the Factory Method lesson: the structure is a twin (a map of key → way of creating), but there we registered recipes (Supplier) and here we register exemplars. The practical difference is that an exemplar can be configured with data, at runtime (a content manager could compose a new template from an admin panel and register it on the fly), whereas a new recipe requires new code. That is Prototype's differential advantage: new "de facto classes" without new classes.
When to use it and when not to
Use it when:
- The new object closely resembles an existing one and copying it is simpler (and more robust) than rebuilding it: "repeat order".
- Exemplars are expensive to assemble (database, computation) and copying amortizes the cost: menu templates.
- Product variants are defined by state configuration rather than distinct behavior: dozens of templates do not justify dozens of classes; one prototype per template does.
- The client must create objects without knowing their concrete classes and you do not want a parallel hierarchy of factories.
Do not use it when:
- Objects are small and cheap to build:
newor a builder is clearer than reasoning about copy depths. - The object graph has circular references or non-copyable resources (connections, open files): copying becomes a swamp.
- What varies between "exemplars" is behavior, not state: that calls for subclasses or Strategy, not copies.
Relation to other patterns (mention only): the prototype registry is a Factory Method/simple factory whose internal mechanism is copying, and an Abstract Factory can be implemented by copying a set of prototypes per family; Memento uses state copies for another purpose (undo); Composite and Decorator produce structures that are often best copied whole.
Common Mistakes and Tips
- Accidental shallow copy: mistake number one. Every mutable field shared unintentionally is a time bomb that explodes far from the copy (the menu template accident). Review the section 3 table field by field.
- Copying too much: duplicating the
Restaurantwhen repeating an order would create two "truths" about the same entity. The right question is never "how do I copy everything?" but "where does this object end?" (composition vs. aggregation). - Copying state that should not travel: unique identifiers, creation dates, workflow state (a copied DELIVERED order cannot be born delivered). Define in the copy constructor what gets reset, not only what gets copied.
- Implementing
Cloneable"because it's the standard": you have seen the catalog of traps. In new code: copy constructor ortoBuilder(). - A registry that hands out the original: if
create()returns the template without copying, the first restaurant to customize its menu redecorates everyone's template. The registry copies always. - Tip: write an independence test for every copyable class: copy, mutate the copy thoroughly, and verify the original did not change (and vice versa). It is cheap and catches accidental shallow copies before production does.
Exercises
Exercise 1: Menu's copy policy
Menu has: String name, Restaurant owner, List<MenuSection> sections (and each MenuSection: String title, List<Product> products, where Product has a price editable per restaurant). Decide, field by field and with justification, what gets shared and what gets deep-copied when copying a template for a new restaurant, and write the copy constructors of Menu and MenuSection.
Exercise 2: hunt the copy bug
This "repeat order" code made it to production. What is the bug and how does it manifest?
public Order repeat(Order previous) {
Order fresh = new Order(previous.getCustomer(), previous.getRestaurant(),
previous.getLines(), // <-- look here
previous.getDeliveryAddress());
return fresh;
}
// Later, in the app flow:
fresh.getLines().removeIf(l -> !l.getProduct().isAvailable());Exercise 3: repeat order, complete
Using the lesson's Order with its copy constructor, implement RepeatOrderService.repeat(Order previous) satisfying: (a) lines whose product is no longer available on the menu are removed from the copy; (b) the time slot is not inherited; (c) if no line survives, throw OrderNotRepeatableException.
Solutions
Solution 1: name — immutable String, the reference is shared (and usually overwritten right afterwards). owner — an aggregated entity; in the copy you do not even copy the template's: you assign the new restaurant (or null until assignment). sections — pure composition: deep copy. products inside each section — since the price is editable per restaurant, each menu needs its own products: depth here too (if products were immutable and the price lived elsewhere, sharing them would be correct: the policy depends on the domain design, there is no mechanical answer).
private Menu(Menu original, Restaurant newOwner) {
this.name = original.name;
this.owner = newOwner;
this.sections = original.sections.stream()
.map(MenuSection::new)
.collect(Collectors.toCollection(ArrayList::new));
}
public MenuSection(MenuSection original) {
this.title = original.title;
this.products = original.products.stream()
.map(Product::new) // Product needs ITS OWN copy constructor
.collect(Collectors.toCollection(ArrayList::new));
}Solution 2: previous.getLines() passes the same list to the new order (a de facto shallow copy). The later removeIf deletes the unavailable products... from the original order too: the customer's history mutates retroactively (their Friday order "loses" lines), and if the original was on screen or in a cache, it displays corrupted data. Typical manifestation: intermittent support tickets saying "my old order changed by itself". Fix: deep copy of the lines in the constructor (or the lesson's copy()).
Solution 3:
public class RepeatOrderService {
public Order repeat(Order previous) {
Order fresh = previous.copy(); // deep copy of the lines
fresh.getModifiableLines()
.removeIf(l -> !l.getProduct().isAvailable()); // (a) affects only the copy
if (fresh.getLines().isEmpty()) {
throw new OrderNotRepeatableException( // (c)
"No product from the order is still available");
}
return fresh.withTimeSlot(null); // (b) the slot is not inherited
}
}The beauty is in (a): we can prune lines from the copy with total peace of mind because the copy is deep; with the version from exercise 2, this very code would corrupt the history.
Conclusion
Prototype completes the creational repertoire with its unique starting point: copying an exemplar instead of instantiating a class, with two PideYa cases where it is the natural solution (repeat order, menu templates) and a registry that turns exemplars into a living catalog, extensible with data and not just with code. What you take away is judgment more than mechanics: the shallow/deep boundary is decided field by field (share entities, copy compositions, reset what should not travel), Cloneable is to be read but not written, and the copy constructor — or toBuilder() — is the healthy vehicle in Java.
With that, all five creational patterns are on the table: instance control, factories for products and for families, step-by-step construction, and copying. What remains is the most important part: knowing how to choose among them, seeing how they combine, and reviewing what ended up installed in every corner of PideYa. That is the job of the module's final lesson: Comparing and Choosing Creational Patterns.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What Are Design Patterns?
- History and Origin of Design Patterns
- Design Principles: SOLID and Other Foundations
- Essential UML for Understanding Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
- Introduction to Creational Patterns
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparing and Choosing Creational Patterns
Module 3: Structural Patterns
- Introduction to Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparing and Choosing Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparing and Choosing Behavioral Patterns
Module 5: Applying Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring with Design Patterns
- Anti-Patterns: When Patterns Become a Problem
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Concurrency Patterns
- Design Patterns in Agile Development
