Prototype Design Pattern

When we need multiple instances of our complex object, we need not build it from scratch every time.

We can build a base version instead as a prototype.

Consequently, we can copy and modify this prototype to create the other instances.

The prototype is another creational design pattern intended to simplify the creation of instances of complex objects.

We can think of it as a variation of the builder pattern for complex objects. It’s because we use the builder to build the object, whereas the prototype says :

“Look ! We may not have to build the whole object. Building it partially on a base version instead may be just enough”.

There can be two very valid reasons:

  1. Not Every Part is Important
  2. Not Every Part is Changing

Hence, you just build only that matters !

In this article we will elaborate on these two use cases to understand the usage of the prototype design pattern better.

 

Use Case 1 : Not Every Part is Important

Lets say we need complex loan objects for our test cases. Each test case needs to test a specific sub-component but, we need the entire loan object for running our test case.

In such a case, we can better have a base version of a valid loan object which we can copy and re-use for our test objects. As a result, each test scenario will just update the part they are interested in testing. No need to build a valid loan object from scratch every time. Here, the base version of our loan object is our prototype.

Moreover, in case we have a family of loan types, we may have to use multiple base versions. And, to make things easy, we may have a factory of different prototypes. The below diagram sums up the use case we have just discussed.

Besides making the creation easy, the prototype provides a common place for accommodating any future changes. Thereby, making our design flexible and more maintainable.

Here is the client code showing how we create a new instance from a copy of the prototype. And, the Prototype factory shows how we can return the copy of the prototype for each new instance.

package spectutz.dp.creational.prototype.loan.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import spectutz.dp.creational.prototype.loan.vo.LoanType;
import spectutz.dp.creational.prototype.loan.vo.PersonalLoan;
import spectutz.dp.creational.prototype.loan.vo.util.IncomeExpenseSummaryBuilder;
import spectutz.dp.creational.prototype.loan.vo.util.LoanDetailBuilder;
import spectutz.dp.creational.prototype.loan.vo.util.LoanPrototypeFactory;

public class PrototypePatternDemo {

	public static void main(String[] args) {
		
		//Step-1 : Get a copy of the base version of the personal loan object
		PersonalLoan personalLoan =
				(PersonalLoan)new LoanPrototypeFactory()
				                    .getPrototype(LoanType.PERSONAL_LOAN);
		
		
		//Step-2 : Build the required components and 
		//update the base version to create a new version
		personalLoan.setLoanDetail(
				new LoanDetailBuilder(500000f, 11.5f, 60).build());
		personalLoan.setIncomeExpenseSummary(
				new IncomeExpenseSummaryBuilder(70000f, 20000f).build());
		
		try {
			System.out.println(new ObjectMapper().writeValueAsString(personalLoan));
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}	
}
//Output - The modified loan object
/*
{
"loanId":0,
"borrowerDetail":"Mr. X - so and so",
"borrowerLiabilities":"X , Y, Z",
"borrowerAssets":"M, N, O",
"employementDetail":"ABCD Corporation Pvt ltd",
"incomeExpenseSummary":{
	"grossMonthlyIncome":70000.0,
	"monthlyLiabilities":20000.0
	},
"loanDetail":{
	"loanAmount":500000.0,
	"tenureInMonths":60,
	"rateOfInterest":11.5
	}
}

*/

package spectutz.dp.creational.prototype.loan.vo.util;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

import spectutz.dp.creational.prototype.loan.vo.ILoan;
import spectutz.dp.creational.prototype.loan.vo.LoanType;
import spectutz.dp.creational.prototype.loan.vo.PersonalLoan;

public class LoanPrototypeFactory {
    private Map<LoanType, ILoan> registry;

    public LoanPrototypeFactory(){
    	registry = new HashMap<LoanType, ILoan>();

    	//Get the file detail from some properties file
    	Map<LoanType, String> loanFiles = new HashMap<LoanType, String>();
    	loanFiles.put(LoanType.PERSONAL_LOAN, "loans/BasePersonalLoan_01.json");
    	
    	//Register the loan prototypes in the factory
    	registerLoanPrototypes(loanFiles);
    }
    
	public ILoan getPrototype(LoanType loanType) {
	    //Clone the prototype and return a copy
		ILoan copyOfLoan = null;
		try {
			if(LoanType.PERSONAL_LOAN == loanType) {
				PersonalLoan personalLoan =(PersonalLoan) registry.get(loanType);				
				copyOfLoan = personalLoan.clone();			
			}
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return copyOfLoan;
	}

    private void registerLoanPrototypes(Map<LoanType, String> loanFiles) {
    	loanFiles.forEach((k,v) ->{
    		//Load and register the loan objects in JSON files
    		ILoan loanPrototype = loadLoanObject(k,v);
    		
    		if(loanPrototype !=null) {
    	    	registry.put(LoanType.PERSONAL_LOAN, loanPrototype);
    		}
    	});    	
    }

	private ILoan loadLoanObject(LoanType loanType, String fileName ) {
		ILoan loanPrototype = null;
		InputStream inputStream = null;
        ObjectMapper objectMapper = new ObjectMapper();
        ClassLoader classLoader = getClass().getClassLoader();
        try {
			inputStream = classLoader.getResourceAsStream(fileName);

			if(LoanType.PERSONAL_LOAN == loanType) {
				loanPrototype = objectMapper.readValue(inputStream, PersonalLoan.class);
	        }
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			if(inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
        return loanPrototype;
	}
}
{
"loanId":0,
"borrowerDetail":"Riyan Dev - so and so",
"borrowerLiabilities":"X , Y, Z",
"borrowerAssets":"M, N, O",
"employementDetail":"ABCD Corporation Pvt ltd - so and so",
"incomeExpenseSummary":{
	"grossMonthlyIncome":50000.0,
	"monthlyLiabilities":10000.0
	},
"loanDetail":{
	"loanAmount":200000.0,
	"tenureInMonths":48,
	"rateOfInterest":11.5
	}
}

Below are the other files related to the demo:

package spectutz.dp.creational.prototype.loan.vo.util;

import spectutz.dp.creational.prototype.loan.vo.LoanDetail;

public class LoanDetailBuilder { 
	private LoanDetail loanDetail = null;

	public LoanDetailBuilder(float loanAmount, float rateOfInterest, int tenureInMonths){
		loanDetail = new LoanDetail();
		loanDetail.setLoanAmount(loanAmount);
		loanDetail.setRateOfInterest(rateOfInterest);
		loanDetail.setTenureInMonths(tenureInMonths);
	}
	
	// Builder methods for other attributes.... 
	
	public LoanDetail build() {
		return loanDetail;
	}	
}
package spectutz.dp.creational.prototype.loan.vo.util;

import spectutz.dp.creational.prototype.loan.vo.IncomeExpenseSummary;

public class IncomeExpenseSummaryBuilder {
	private IncomeExpenseSummary incomeExpenseSummary;
	
	public IncomeExpenseSummaryBuilder(float grossMonthlyIncome, float monthlyLiabilities) {
		incomeExpenseSummary = new IncomeExpenseSummary();
		incomeExpenseSummary.setGrossMonthlyIncome(grossMonthlyIncome);
		incomeExpenseSummary.setMonthlyLiabilities(monthlyLiabilities);
	}
	// Builder methods for other attributes....
	
	public IncomeExpenseSummary build() {
		return incomeExpenseSummary;
	}
}
package spectutz.dp.creational.prototype.loan.vo;

public class PersonalLoan implements ILoan{
	
	//We are using string to keep the example simple...
	private long loanId;
	private String borrowerDetail;
	private String borrowerLiabilities;
	private String borrowerAssets;
	private String employementDetail;
	
	private IncomeExpenseSummary incomeExpenseSummary;
	private LoanDetail loanDetail;
	
	public PersonalLoan clone() throws CloneNotSupportedException {
		return (PersonalLoan)super.clone();
	}
	public long getLoanId() {
		return loanId;
	}

	public void setLoanId(long loanId) {
		this.loanId = loanId;
	}
	public String getBorrowerDetail() {
		return borrowerDetail;
	}

	public void setBorrowerDetail(String borrowerDetail) {
		this.borrowerDetail = borrowerDetail;
	}

	public String getBorrowerLiabilities() {
		return borrowerLiabilities;
	}

	public void setBorrowerLiabilities(String borrowerLiabilities) {
		this.borrowerLiabilities = borrowerLiabilities;
	}

	public String getBorrowerAssets() {
		return borrowerAssets;
	}

	public void setBorrowerAssets(String borrowerAssets) {
		this.borrowerAssets = borrowerAssets;
	}

	public String getEmployementDetail() {
		return employementDetail;
	}

	public void setEmployementDetail(String employementDetail) {
		this.employementDetail = employementDetail;
	}

	public IncomeExpenseSummary getIncomeExpenseSummary() {
		return incomeExpenseSummary;
	}

	public void setIncomeExpenseSummary(IncomeExpenseSummary incomeExpenseSummary) {
		this.incomeExpenseSummary = incomeExpenseSummary;
	}

	public LoanDetail getLoanDetail() {
		return loanDetail;
	}

	public void setLoanDetail(LoanDetail loanDetail) {
		this.loanDetail = loanDetail;
	}
}
package spectutz.dp.creational.prototype.loan.vo;

public class LoanDetail implements ILoan{
	private static final long serialVersionUID = -6128596301867791695L;

	private float loanAmount;
	private int tenureInMonths;
	private float rateOfInterest;
	
	public float getLoanAmount() {
		return loanAmount;
	}
	public void setLoanAmount(float loanAmount) {
		this.loanAmount = loanAmount;
	}
	public int getTenureInMonths() {
		return tenureInMonths;
	}
	public void setTenureInMonths(int tenureInMonths) {
		this.tenureInMonths = tenureInMonths;
	}
	public float getRateOfInterest() {
		return rateOfInterest;
	}
	public void setRateOfInterest(float rateOfInterest) {
		this.rateOfInterest = rateOfInterest;
	}
	
	
}
package spectutz.dp.creational.prototype.loan.vo;

public class IncomeExpenseSummary implements ILoan{
	private static final long serialVersionUID = 1L;

	private float grossMonthlyIncome; 
	private float monthlyLiabilities;
	
	 public float getGrossMonthlyIncome() {
		return grossMonthlyIncome;
	}
	public void setGrossMonthlyIncome(float grossMonthlyIncome) {
		this.grossMonthlyIncome = grossMonthlyIncome;
	}
	public float getMonthlyLiabilities() {
		return monthlyLiabilities;
	}
	public void setMonthlyLiabilities(float monthlyLiabilities) {
		this.monthlyLiabilities = monthlyLiabilities;
	} 	
}
package spectutz.dp.creational.prototype.loan.vo;

import java.io.Serializable;

public interface  ILoan extends Serializable, Cloneable {}
package spectutz.dp.creational.prototype.loan.vo;

public enum LoanType {	
	PERSONAL_LOAN, CAR_LOAN, TWO_WHELLER_LOAN;
}

Use Case 2 : Not Every Part is Changing

This is an example shown under the builder design pattern which can show the key difference between these two patterns.

The CacheConfiguration which uses the builder pattern, builds the part which the user wants to change. But, the Cache here is the common base template acting as a prototype.

The prototype as a template comes with all the default configurations. And, it also provides a common place to add or update features which will be shared by its instances.

//Create a singleton CacheManager using defaults
CacheManager manager = CacheManager.create();

// CacheConfiguration : Update as many configs as you want where the rest will run with the defaults.
Cache sampleCache= new Cache(
  new CacheConfiguration("sampleCache", maxEntriesLocalHeap)
    .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
    .eternal(false)
    .timeToLiveSeconds(60)
    .timeToIdleSeconds(30)
    .diskExpiryThreadIntervalSeconds(0)
    .persistence(new PersistenceConfiguration().strategy(Strategy.LOCALTEMPSWAP)));

  manager.addCache(sampleCache);

Conclusion

The prototype is another most confusing design pattern. Saving the initialization cost through cloning is kind of a common understanding. But, when we think of it as a common base version or a template, it provides a more complementary feature over the other creational patterns.

First of all, as a common base version or a template, it greatly simplifies the creation of multiple variations of the complex object.

Secondly, the prototype providing a common place to accommodate our changes, it makes our design more maintainable and flexible to extend.