This pattern is visible in everyday coding, refactoring and design. Put simply, this pattern helps you solve many problems.
You can enjoy part one here.
Original Problem:
We want to allow recurrences in Google Calendar. Specifically, users should be able to set a recurring event and update it dynamically.

1. Recurring updates create two recurrence threads
The New Problem:
We need a way to create recurrences with nearly identical properties as the previous recurrence object.
class Recurrence {
String title;
Date startDate;
Date endDate;
}Here are three common approaches.
Approach 1: (Just do it)
The idea to create a new object using all parameters mentioned in the update request. We fill all missing update parameters by using the original recurrence.
We effectively overwrite the oldRecurrence values with those mentioned in UpdateRequest.
Recurrence processUpdate(Recurrence originalRecurrence, UpdateRequest request) {
Recurrence newEvent = new Recurrence();
newEvent.title = request.title == null ? originalRecurrence.title : request.title;
newEvent.startDate = request.startDate == null ? originalRecurrence.startDate : request.startDate;
newEvent.endDate = request.endDate == null ? originalRecurrence.endDate : request.endDate;
return newEvent;
}As you can see, this type of code will make your head spin, and not in a good way.
With more parameters added to the recurrence, this code will be modified again and again, breaking the open-closed principle and creating a bloated function that is hard to test or debug.
Please don't do this unless your code is expected to run just once.
Approach 2: (Use immutable objects)
You could use the concept of immutability to make update operations simple. Every update results in a new object being created!
class Recurrence {
String title;
Date startDate;
Date endDate;
public Recurrence setTitle(String title) {
Recurrence newObject = new Recurrence(this.title, this.startDate, this.endDate);
this.title = title;
return newObject;
}
public void setStartDate(Date startDate) {
Recurrence newObject = new Recurrence(this.title, this.startDate, this.endDate);
this.startDate = startDate;
return newObject;
}
public void setEndDate(Date endDate) {
Recurrence newObject = new Recurrence(this.title, this.startDate, this.endDate);
this.endDate = endDate;
return newObject;
}
}As you can see, this is simpler to implement and doesn't lead to massive code bloat. It even respects the open-closed principle.
However, the code forces new objects to be created on every mutation. That seems a bit extreme.
Worse, these mutations can't be composed together without creating a large chain of mutations.
For example:
Recurrence processUpdate(Recurrence originalRecurrence, UpdateRequest request) {
Recurrence newEvent = originalRecurrence;
newEvent.setTitle(request.title); // new object created
newEvent.setStartDate(request.startDate); // new object created
newEvent.setEndDate(request.endDate); // new object created
return newEvent;
}We had to create 3 new objects for a single mutation! As our objects get more complex, the problem gets worse.
For our use-case of fast updates, this approach isn't suitable.
Approach 3: (Prototype design pattern)
class Recurrence {
String title;
Date startDate;
Date endDate;
Recurrence clone(UpdateRequest request) {
Recurrence r = new Recurrence(title, startDate, endDate);
if(request.title != null) {
r.setTitle(request.title);
}
if(request.startDate != null) {
r.setStartDate(request.startDate);
}
if(request.endDate != null) {
r.setEndDate(request.endDate);
}
}
}That's it!
Clone the original object, Set the new parameters on this object using standard setter methods, and voila.
This keeps the code short, simple and easily maintainable. The code is also easier to understand, is contained within the Recurrence object (Respecting the single-responsibility principle).
- Clone the object
- Use setter methods to set new parameters
- Return the object.
Homework:
- There is still the problem of iterating through all non-null params in UpdateRequest.
- Could you think of how you would create a clone for a deeply nested, complex object?
class Recurrence {
String title;
Date startDate;
Date endDate;
Response[] responses;
User[] invitees;
}How would you do a deep copy of the object without copying its references?
*Hint: Think of serializing and deserializing objects. To create clones and filter through non-null update parameters, JSON manipulation could help!*
Let me know your thoughts in the comments below. If you liked this blog, register to get for free updates in future!
If you like my System Design Content, check out this video course.