...
Method chaining is a useful design pattern for building an object and setting its optional fields. A class that supports method chaining provides several setter methods that each return the this
reference. However, if accessed concurrently, a thread may observe shared fields to contain inconsistent values. This noncompliant code example shows the JavaBeans pattern, which is not thread-safe.
Code Block | ||
---|---|---|
| ||
final class USCurrency { // Change requested, denomination (optional fields) private int quarters = 0; private int dimes = 0; private int nickels = 0; private int pennies = 0; public USCurrency() {} // Setter methods public USCurrency setQuarters(int quantity) { quarters = quantity; return this; } public USCurrency setDimes(int quantity) { dimes = quantity; return this; } public USCurrency setNickels(int quantity) { nickels = quantity; return this; } public USCurrency setPennies(int quantity) { pennies = quantity; return this; } } // Client code: class exampleClientCodeExampleClientCode { private final USCurrency currency = new USCurrency(); // ... public exampleClientCodeExampleClientCode() { Thread t1 = new Thread(new Runnable() { @Override public void run() { currency.setQuarters(1).setDimes(1); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { currency.setQuarters(2).setDimes(2); } }); t2.start(); //... } } |
...
This compliant solution uses the variant of the Builder pattern [Gamma 1995], suggested by Bloch [Bloch 2008], to ensure the thread-safety and atomicity of object creation.
Code Block | ||
---|---|---|
| ||
final class USCurrency { private final int quarters; private final int dimes; private final int nickels; private final int pennies; public USCurrency(Builder builder) { this.quarters = builder.quarters; this.dimes = builder.dimes; this.nickels = builder.nickels; this.pennies = builder.pennies; } // Static class member public static class Builder { private int quarters = 0; private int dimes = 0; private int nickels = 0; private int pennies = 0; public static Builder newInstance() { return new Builder(); } private Builder() {} // Setter methods public Builder setQuarters(int quantity) { this.quarters = quantity; return this; } public Builder setDimes(int quantity) { this.dimes = quantity; return this; } public Builder setNickels(int quantity) { this.nickels = quantity; return this; } public Builder setPennies(int quantity) { this.pennies = quantity; return this; } public USCurrency build() { return new USCurrency(this); } } } // Client code: class exampleClientCodeExampleClientCode { private volatile USCurrency currency; // ... public exampleClientCodeExampleClientCode() { Thread t1 = new Thread(new Runnable() { @Override public void run() { currency = USCurrency.Builder.newInstance(). setQuarters(1).setDimes(1).build(); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { currency = USCurrency.Builder.newInstance(). setQuarters(2).setDimes(2).build(); } }); t2.start(); //... } } |
...
[API 2006] |
|
Item 2, Consider a builder when faced with many constructor parameters |