Next up in our Java series, I’m excited to cover the Prototype Design Pattern.
The Creational Design Pattern is a group of patterns that are extremely useful for creating an object. Object creation often depends on various contexts such as the number of parameters passed in and the base conditions. As one of the popular patterns in the Creational group, the Prototype pattern is highly effective for creating complex objects. Let’s get into designing a Prototype pattern in Java!
1. What is a Prototype Design Pattern?
Let’s take a closer look at how the Prototype pattern works and what makes it tick with an example of creating different types of bank cards.
Advances in society drive cashless transactions to the forefront in large cities. Most people have a bank account these days. You can easily use your ATM card to make payments.
There are a few kinds of ATM cards – like MasterCard, and Visa. Each bank offers different types of cards to different groups of customers – like VIP cards and CashBack cards. Physically, these cards are made of the same stuff, have the same size and shape, and contain common information. But each card still has its unique features, like the color and CVV background, you know?
A small topic for developers is how to efficiently create these ATM cards without excessive effort. Are you looking for a solution? One approach you could consider is the Builder pattern. But, when dealing with an object that has many parameters and complex logic, the Builder pattern might not be the best option. I prefer copying rather than starting from scratch.
So, I will use one of the developers’ top skills: copy-pasting. The Prototype pattern will help maximize the efficiency of object creation by cloning from an existing template. After that, I can modify it according to the specific context.
At this point, you get why we’re using this pattern. Now, let’s get into how actually to design and implement this pattern. First, check out the Prototype pattern’s class diagram – it’ll give us the big picture.
2. Prototype Design Pattern Diagram
In the diagram above, the main components are three classes: Client, Prototype, and SubPrototype.
– Client Class: Represents the class that uses the Prototype object to clone SubPrototypes.
– Prototype Class: An interface or abstract class containing the abstract clone() method.
– SubPrototype Class: Represents the subclasses where we will implement the clone() method.
3. Prototype Pattern Implementation
We have reviewed the class diagram. Now, let’s dive into the details of how to implement this pattern through the example of creating different types of bank cards.
Let’s start with the BankCard class and use it to build some specific types of cards, like PlatinumCard and DiamondCard. These are for two different types of customers at a bank, and we’ll create them using the Prototype pattern.
First, we create a BankCard class, which represents various types of bank cards (Visa, MasterCard, etc.). This class contains common attributes such as CardNumber, HolderName, CardType, and so on. But, creating a BankCard object through the constructor is a real hassle. You have to fill in a ton of fields, which means more work for both the developer and the system. So I’ll go with the cloning mechanism from the Cloneable interface instead.
Here are the details of the BankCard class:
public class BankCard implements Cloneable{
public String cardNumber;
public String holderName;
public String cardType;
public BigDecimal amount;
public BankCard(String cardType){
this.cardType = cardType;
}
@Override
protected BankCard clone() {
try {
return (BankCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return “BankCard [cardNumber=” + cardNumber + “,
holderName=” + holderName + “,
cardType=”+ cardType + “, amount=”+amount+” ]”;
}
// Getters and Setters
}
Looking at the class diagram, it seems I need a CardClient class. This class will use the Prototype pattern to generate multiple card types for the same customer by utilizing the clone() method of the BankCard class.
public class CardClient{
public static void main(String[] args){
BankCard platinumCard = new BankCard(“master”);
platinumCard.setHolderName(“Tom”);
platinumCard.setCardNumber(“1234”);
BankCard diamondCard = masterCard.clone();
diamondCard.setCardType(“visa”);
System.out.println(platinumCard);
System.out.println(diamondCard);
}
}
The Prototype pattern implementation is straightforward and lacks intricacy. You might be wondering a few things at this point.
The first question is: If I update an attribute of platinumCard, will the corresponding attribute of diamondCard also change? Since diamondCard was created by copying the information from platinumCard.
Good question. The answer is clearly “No”. The clone() method creates a deep copy, which means modifying the new object has no impact on the original.
The second question is: What is the difference between the clone() method and the “=” operator?
BankCard diamondCard = platinumCard.clone() vs BankCard diamondCard = platinumCard;
Using the “=” operator means that the diamondCard object references the platinumCard object. If platinumCard changes information, this will cause diamondCard to change as well (shallow copy).
I’ll write an article about Deep Copy and Shallow Copy to break down how they’re different.
I’ve applied this pattern in a bunch of projects, so what’s next? Let me break down the good and bad things I’ve learned from doing it myself.
4. Pros and Cons of Prototype Design Pattern
Pros of Prototype
Here’s one thing developers love about the Prototype pattern: it makes it super easy to create new objects based on ones you already have. Developers don’t have to put in as much effort, and the system gets a big performance boost.
The Prototype pattern comes in handy when you need to build objects with tricky logic. This pattern also helps you avoid making too many subclasses you don’t need.
In my real-world projects, I have combined the Prototype pattern with the Factory pattern to implement a rather complex feature: generating various types of contracts for clients in financial systems, such as insurance policies or mortgage and unsecured loan agreements. I also went with a Map collection to make my Prototype pattern much more efficient.
public class ContractFactory{
public Map contractMaps = new HashMap<>();
public void extendContract(String customerId){
Contract extended = null;
if(contractMaps.containsKey(customerId)) {
Contract original = contractMaps.get(customerId);
extended = original.clone();
extended.setRenewDate(new Date());
}else {
extended = new Contract();
}
contractMaps.put(customerId, extended);
}
}
Cons of Prototype
The Prototype pattern is perfect for when you need to create objects that have a lot in common. So you need to be careful when you’re making special objects that require a lot of updates to many fields.
When you create a new object by cloning an existing one, chances are it’ll have extra stuff it doesn’t need – leftovers from the original object.
5. Conclusion
The prototype design pattern is super useful when you need to create complicated objects. But there are some downsides too, which I’ve covered in this article. Let’s be real, nothing is 100% perfect. So you gotta think carefully about your options before picking a pattern to get the best result.