Josh Bloch first
talked about some enhancements to the Builder Pattern back in 2006. Then
Xavi and
Mario took it a bit further with some nice clear examples.
I stumbled on these articles when I began looking more into how to make Java model objects that were both immutable, and yet still convenient. An immutable builder has come in handy for me in several situations - hence I thought I would pass on this simple and useful pattern too.
Here is an example of my version of the "enhanced immutable Builder" (also apparently called the
"Essence" pattern in C/C#/etc circles).
package com.model.account;
import java.util.Date;
import com.model.AbstractBuilder;
public final class ImmutableAccount {
private final Date date;
private double referenceValue;
private double budgetValue;
private double benchmarkValue;
/**
* Public Builder internal class that
* API users must use to construct the object - with this
* approach can make an immutable object AND allow the use of
* the convenient builder creational pattern.
*
* NOTE - Builder constructor contains REQUIRED parameters.
* NOTE - OPTIONAL parameters are available in the builder sequence.
* NOTE - The build() method returns the final instance of the object.
*
*/
public static class Builder extends AbstractBuilder {
private ImmutableAccount account;
/**
* Internal Builder class.
*
* @param id
* @param description
* @param date
*/
// REQUIRED fields in the Builder constructor
public Builder(Date date) {
this.account = new ImmutableAccount(date);
}
// OPTIONAL fields as Builder methods
public Builder referenceValue(double val) {
this.checkBuilt();
this.account.referenceValue = val;
return this;
}
public Builder budgetValue(double val) {
this.checkBuilt();
this.account.budgetValue = val;
return this;
}
public Builder benchmarkValue(double val) {
this.checkBuilt();
this.account.benchmarkValue = val;
return this;
}
// the BUILD method returns the instance
public ImmutableAccount build() {
this.checkBuilt();
this.isBuilt = true;
return this.account;
}
}
/**
* Main method object has a PRIVATE constructor intentionally -
* require that the Builder is used to create this object.
*
* @param date
*/
private ImmutableAccount(Date date) {
this.date = date;
}
//
// accessors
//
public String getId() {
return this.id;
}
public String getDescription() {
return this.description;
}
public double getBudgetValue() {
return this.budgetValue;
}
public double getBenchmarkValue() {
return this.benchmarkValue;
}
public double getReferenceValue() {
return this.referenceValue;
}
//
// mutators that DO NOT mutate ;) - convenience that return new immutable object replacements
// we make sure not to name or call these "set"ters so that it doesn't potentially confuse (especially with tools)
//
public ImmutableAccount updateDate(Date value) {
return new ImmutableAccount.Builder(this.id, this.description, value).benchmarkValue(this.benchmarkValue)
.budgetValue(this.budgetValue).referenceValue(this.referenceValue).build();
}
public ImmutableAccount updateBudgetValue(double value) {
return new ImmutableAccount.Builder(this.id, this.description, this.date).benchmarkValue(this.benchmarkValue)
.budgetValue(value).referenceValue(this.referenceValue).build();
}
public ImmutableAccount updateBenchmarkValue(double value) {
return new ImmutableAccount.Builder(this.id, this.description, this.date).benchmarkValue(value)
.budgetValue(this.budgetValue).referenceValue(this.referenceValue).build();
}
public ImmutableAccount updateReferenceValue(double value) {
return new ImmutableAccount.Builder(this.id, this.description, this.date).benchmarkValue(this.benchmarkValue)
.budgetValue(this.budgetValue).referenceValue(value).build();
}
}
And the AbstractBuilder the Builder extends is ultra simple too:
package com.model;
public abstract class AbstractBuilder {
protected boolean isBuilt;
public void checkBuilt() {
if (this.isBuilt) {
throw new IllegalStateException("The object cannot be modified after built");
}
}
}
Lastly, here is a test case that further demonstrates how you would actually use the immutable object with the Builder to create an instance:
package com.model.account;
import java.util.Date;
import junit.framework.Assert;
import com.axiomainc.test.AbstractTestCase;
public class ImmutableAccountTest extends AbstractTestCase {
public void testBuilder() {
ImmutableAccount account = new ImmutableAccount.Builder(new Date()).benchmarkValue(0.0).budgetValue(1000.0).referenceValue(10.0).build();
System.out.println("account PRE - " + account);
account = account.updateBenchmarkValue(2000.0);
System.out.println("account POST - " + account);
Assert.assertEquals(2000.0, account.getBenchmarkValue());
}
}
Chatter
1 week 6 hours ago
1 week 15 hours ago
1 week 1 day ago
1 week 4 days ago
1 week 6 days ago
1 week 6 days ago
2 weeks 4 hours ago
2 weeks 7 hours ago
2 weeks 7 hours ago
2 weeks 9 hours ago