The Prototype Pattern is a creational design pattern that allows you to create new objects by copying an existing object (the prototype) rather than creating them from scratch using the new keyword.
The Problem
Imagine your website manages Server Configurations. Some configurations are extremely "expensive" or "heavy" to create—for example, they might require complex database queries, network handshakes, or heavy mathematical calculations to initialize their state.
If you need 100 identical server instances, running that heavy initialization 100 times is a waste of resources.
The Solution: Cloning
Instead of initializing a new object from zero, you take an already initialized object (the "Prototype") and clone it. Since the clone is a literal copy, it starts with all the pre-calculated data already in place.
Step-by-Step Java Implementation
In Java, the standard way to implement this is by using the Cloneable interface and overriding the clone() method.
1. The Prototype Interface/Class
We define a base class that implements Cloneable.
// ServerConfig.java
public abstract class ServerConfig implements Cloneable {
protected String os;
protected String ram;
// A heavy operation simulated here
public abstract void loadResources();
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
2. Concrete Prototypes
These are the specific objects we want to copy.
// WebServerConfig.java
public class WebServerConfig extends ServerConfig {
public WebServerConfig() {
this.os = "Ubuntu 22.04";
this.ram = "16GB";
}
@Override
public void loadResources() {
System.out.println("Loading heavy web assets and security certificates...");
}
}
// DatabaseServerConfig.java
public class DatabaseServerConfig extends ServerConfig {
public DatabaseServerConfig() {
this.os = "Debian 12";
this.ram = "64GB";
}
@Override
public void loadResources() {
System.out.println("Initializing large database cache and connection pools...");
}
}
Full Code for Testing
This example demonstrates a "Registry" where we store our prototypes and clone them whenever needed.
// Save as PrototypeTest.java
import java.util.HashMap;
import java.util.Map;
`abstract class ServerConfig implements Cloneable {
String type;
String os;
abstract void showConfig();
@Override
public ServerConfig clone() {
try {
return (ServerConfig) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
class WebServer extends ServerConfig {
public WebServer() {
this.type = "Web Server";
this.os = "Nginx on Linux";
}
@Override
void showConfig() {
System.out.println("Config: " + type + " running " + os);
}
}
class DatabaseServer extends ServerConfig {
public DatabaseServer() {
this.type = "Database Server";
this.os = "PostgreSQL on FreeBSD";
}
@Override
void showConfig() {
System.out.println("Config: " + type + " running " + os);
}
}
// A Registry to manage prototypes
class ConfigRegistry {
private static Map<String, ServerConfig> prototypes = new HashMap<>();
static {
prototypes.put("web", new WebServer());
prototypes.put("db", new DatabaseServer());
}
public static ServerConfig getClone(String key) {
return prototypes.get(key).clone();
}
}
public class PrototypeTest {
public static void main(String[] args) {
System.out.println("--- Prototype Pattern Test ---\n");
// We don't use 'new WebServer()'. We clone the existing one.
ServerConfig myFirstWeb = ConfigRegistry.getClone("web");
myFirstWeb.showConfig();
ServerConfig mySecondWeb = ConfigRegistry.getClone("web");
// We can modify the clone without affecting the original prototype
mySecondWeb.os = "Apache on Windows";
mySecondWeb.showConfig();
ServerConfig myDb = ConfigRegistry.getClone("db");
myDb.showConfig();
System.out.println("\n(Verification: Objects are separate instances: " + (myFirstWeb != mySecondWeb) + ")");
}
}
Why use Prototype?
- Performance: It is usually much faster to clone an object than to create a new one if the initialization is resource-heavy.
- Reduced Subclassing: If you have many objects that only differ by their internal state, you can just have a few prototypes and clone/modify them rather than creating a class for every single variation.
- Hiding Complexity: The client doesn't need to know how to construct a complex object; they just ask for a copy.
Important Note: Shallow vs. Deep Copy
- Shallow Copy: The default
clone()copies primitive values, but if your object contains a reference to another object (like aList), both the original and the clone will point to the same list. - Deep Copy: If you want a truly independent copy of internal objects, you must manually clone those internal objects inside your
clone()method.
Is this clear for your tutorial, or should I expand on the "Deep Copy" aspect?