Created
October 20, 2013 19:46
-
-
Save sandermak/7074352 to your computer and use it in GitHub Desktop.
Code showing alternative to the standard Builder pattern implementation in Java. Also see blogpost at http://branchandbound.net/blog/java/2013/10/failed-experiment-improving-builder-pattern/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.branchandbound.builder; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
public class PizzaOrder { | |
private int size; | |
private boolean pepperoni; | |
private boolean chicken; | |
private boolean mushroom; | |
private boolean peppers; | |
private String cheese; | |
private String sauce; | |
private String orderFor; | |
private PizzaOrder() { | |
// Prevent direct instantiation | |
} | |
public static Builder pizzaOrder(int size, String sauce, String orderFor) { | |
return new PizzaOrder().new Builder(size, sauce, orderFor); | |
} | |
public class Builder { | |
private AtomicBoolean build = new AtomicBoolean(false); | |
public Builder(int _size, String _sauce, String _orderFor) { | |
size = _size; | |
sauce = _sauce; | |
orderFor = _orderFor; | |
} | |
public Builder withPepperoni() { | |
throwIfBuild(); | |
pepperoni = true; | |
return this; | |
} | |
public Builder withChicken() { | |
throwIfBuild(); | |
chicken = true; | |
return this; | |
} | |
public Builder withMushroom() { | |
throwIfBuild(); | |
mushroom = true; | |
return this; | |
} | |
public Builder withPeppers() { | |
throwIfBuild(); | |
peppers = true; | |
return this; | |
} | |
public Builder withCheese(String _cheese) { | |
throwIfBuild(); | |
cheese = _cheese; | |
return this; | |
} | |
public PizzaOrder build() { | |
if (build.compareAndSet(false, true)) { | |
// check consistency here... | |
return PizzaOrder.this; | |
} else { | |
throw new IllegalStateException("Build may only be called once!"); | |
} | |
} | |
private void throwIfBuild() { | |
if (build.get()) { | |
throw new IllegalStateException("Cannot modify builder after calling build()"); | |
} | |
} | |
} | |
public int getSize() { | |
return size; | |
} | |
public boolean isPepperoni() { | |
return pepperoni; | |
} | |
public boolean isChicken() { | |
return chicken; | |
} | |
public boolean isMushroom() { | |
return mushroom; | |
} | |
public boolean isPeppers() { | |
return peppers; | |
} | |
public String getCheese() { | |
return cheese; | |
} | |
public String getSauce() { | |
return sauce; | |
} | |
public String getOrderFor() { | |
return orderFor; | |
} | |
@Override | |
public int hashCode() { | |
final int prime = 31; | |
int result = 1; | |
result = prime * result + ((cheese == null) ? 0 : cheese.hashCode()); | |
result = prime * result + (chicken ? 1231 : 1237); | |
result = prime * result + (mushroom ? 1231 : 1237); | |
result = prime * result + ((orderFor == null) ? 0 : orderFor.hashCode()); | |
result = prime * result + (pepperoni ? 1231 : 1237); | |
result = prime * result + (peppers ? 1231 : 1237); | |
result = prime * result + ((sauce == null) ? 0 : sauce.hashCode()); | |
result = prime * result + size; | |
return result; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (this == obj) | |
return true; | |
if (obj == null) | |
return false; | |
if (getClass() != obj.getClass()) | |
return false; | |
PizzaOrder other = (PizzaOrder) obj; | |
if (cheese == null) { | |
if (other.cheese != null) | |
return false; | |
} else if (!cheese.equals(other.cheese)) | |
return false; | |
if (chicken != other.chicken) | |
return false; | |
if (mushroom != other.mushroom) | |
return false; | |
if (orderFor == null) { | |
if (other.orderFor != null) | |
return false; | |
} else if (!orderFor.equals(other.orderFor)) | |
return false; | |
if (pepperoni != other.pepperoni) | |
return false; | |
if (peppers != other.peppers) | |
return false; | |
if (sauce == null) { | |
if (other.sauce != null) | |
return false; | |
} else if (!sauce.equals(other.sauce)) | |
return false; | |
if (size != other.size) | |
return false; | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.branchandbound.builder; | |
public class PizzaOrderOldStyle { | |
private int size; | |
private boolean pepperoni; | |
private boolean chicken; | |
private boolean mushroom; | |
private boolean peppers; | |
private String cheese; | |
private String sauce; | |
private String orderFor; | |
public static Builder pizzaOrder(int size, String sauce, String orderFor) { | |
return new PizzaOrderOldStyle.Builder(size, sauce, orderFor); | |
} | |
public static class Builder { | |
private int size; | |
private boolean pepperoni; | |
private boolean chicken; | |
private boolean mushroom; | |
private boolean peppers; | |
String cheese; | |
private String sauce; | |
private String orderFor; | |
public Builder(int size, String sauce, String orderFor) { | |
this.size = size; | |
this.sauce = sauce; | |
this.orderFor = orderFor; | |
} | |
public Builder withPepperoni() { | |
this.pepperoni = true; | |
return this; | |
} | |
public Builder withChicken() { | |
this.chicken = true; | |
return this; | |
} | |
public Builder withMushroom() { | |
this.mushroom = true; | |
return this; | |
} | |
public Builder withPeppers() { | |
this.peppers = true; | |
return this; | |
} | |
public Builder withCheese(String cheese) { | |
this.cheese = cheese; | |
return this; | |
} | |
public PizzaOrderOldStyle build() { | |
// check consistency here... | |
return new PizzaOrderOldStyle(this); | |
} | |
} | |
private PizzaOrderOldStyle(Builder builder) { | |
size = builder.size; | |
pepperoni = builder.pepperoni; | |
chicken = builder.chicken; | |
mushroom = builder.mushroom; | |
peppers = builder.peppers; | |
cheese = builder.cheese; | |
sauce = builder.sauce; | |
orderFor = builder.orderFor; | |
} | |
public int getSize() { | |
return size; | |
} | |
public boolean isPepperoni() { | |
return pepperoni; | |
} | |
public boolean isChicken() { | |
return chicken; | |
} | |
public boolean isMushroom() { | |
return mushroom; | |
} | |
public boolean isPeppers() { | |
return peppers; | |
} | |
public String getCheese() { | |
return cheese; | |
} | |
public String getSauce() { | |
return sauce; | |
} | |
public String getOrderFor() { | |
return orderFor; | |
} | |
@Override | |
public int hashCode() { | |
final int prime = 31; | |
int result = 1; | |
result = prime * result + ((cheese == null) ? 0 : cheese.hashCode()); | |
result = prime * result + (chicken ? 1231 : 1237); | |
result = prime * result + (mushroom ? 1231 : 1237); | |
result = prime * result + ((orderFor == null) ? 0 : orderFor.hashCode()); | |
result = prime * result + (pepperoni ? 1231 : 1237); | |
result = prime * result + (peppers ? 1231 : 1237); | |
result = prime * result + ((sauce == null) ? 0 : sauce.hashCode()); | |
result = prime * result + size; | |
return result; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (this == obj) | |
return true; | |
if (obj == null) | |
return false; | |
if (getClass() != obj.getClass()) | |
return false; | |
PizzaOrderOldStyle other = (PizzaOrderOldStyle) obj; | |
if (cheese == null) { | |
if (other.cheese != null) | |
return false; | |
} else if (!cheese.equals(other.cheese)) | |
return false; | |
if (chicken != other.chicken) | |
return false; | |
if (mushroom != other.mushroom) | |
return false; | |
if (orderFor == null) { | |
if (other.orderFor != null) | |
return false; | |
} else if (!orderFor.equals(other.orderFor)) | |
return false; | |
if (pepperoni != other.pepperoni) | |
return false; | |
if (peppers != other.peppers) | |
return false; | |
if (sauce == null) { | |
if (other.sauce != null) | |
return false; | |
} else if (!sauce.equals(other.sauce)) | |
return false; | |
if (size != other.size) | |
return false; | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.branchandbound.builder.test; | |
import static net.branchandbound.builder.PizzaOrderOldStyle.pizzaOrder; | |
import static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.assertFalse; | |
import static org.junit.Assert.assertTrue; | |
import net.branchandbound.builder.PizzaOrderOldStyle; | |
import net.branchandbound.builder.PizzaOrderOldStyle.Builder; | |
import org.junit.Test; | |
public class PizzaOrderOldStyleTest { | |
@Test | |
public void valuesShouldBeRetained() { | |
PizzaOrderOldStyle pizzaOrder = | |
pizzaOrder(10, "tomato", "Sander") | |
.withPepperoni() | |
.withMushroom() | |
.withCheese("parmesan").build(); | |
assertEquals(10, pizzaOrder.getSize()); | |
assertEquals("tomato", pizzaOrder.getSauce()); | |
assertEquals("Sander", pizzaOrder.getOrderFor()); | |
assertTrue(pizzaOrder.isPepperoni()); | |
assertFalse(pizzaOrder.isChicken()); | |
assertTrue(pizzaOrder.isMushroom()); | |
assertFalse(pizzaOrder.isPeppers()); | |
assertEquals("parmesan", pizzaOrder.getCheese()); | |
} | |
@Test | |
public void testEqualsHashCode() { | |
Builder builder = pizzaOrder(10, "tomato", "Sander").withPepperoni().withMushroom().withCheese("parmesan"); | |
PizzaOrderOldStyle a = builder.build(); | |
PizzaOrderOldStyle b = builder.build(); | |
assertEquals(a, b); | |
assertEquals(a.hashCode(), b.hashCode()); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.branchandbound.builder.test; | |
import static net.branchandbound.builder.PizzaOrder.pizzaOrder; | |
import static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.assertFalse; | |
import static org.junit.Assert.assertTrue; | |
import net.branchandbound.builder.PizzaOrder; | |
import net.branchandbound.builder.PizzaOrder.Builder; | |
import org.junit.Test; | |
public class PizzaOrderTest { | |
@Test | |
public void valuesShouldBeRetained() { | |
PizzaOrder pizzaOrder = | |
pizzaOrder(10, "tomato", "Sander").withPepperoni().withMushroom() | |
.withCheese("parmesan").build(); | |
assertEquals(10, pizzaOrder.getSize()); | |
assertEquals("tomato", pizzaOrder.getSauce()); | |
assertEquals("Sander", pizzaOrder.getOrderFor()); | |
assertTrue(pizzaOrder.isPepperoni()); | |
assertFalse(pizzaOrder.isChicken()); | |
assertTrue(pizzaOrder.isMushroom()); | |
assertFalse(pizzaOrder.isPeppers()); | |
assertEquals("parmesan", pizzaOrder.getCheese()); | |
} | |
@Test | |
public void testEqualsHashCode() { | |
PizzaOrder a = getCorrectlyFilledBuilder().build(); | |
PizzaOrder b = getCorrectlyFilledBuilder().build(); | |
assertEquals(a, b); | |
assertEquals(a.hashCode(), b.hashCode()); | |
} | |
@Test(expected = IllegalStateException.class) | |
public void cannotBuildTwice() { | |
Builder builder = getCorrectlyFilledBuilder(); | |
builder.build(); | |
builder.build(); | |
} | |
@Test(expected = IllegalStateException.class) | |
public void cannotUseBuilderAfterBuild() { | |
Builder correctlyFilledBuilder = getCorrectlyFilledBuilder(); | |
correctlyFilledBuilder.build(); | |
correctlyFilledBuilder.withCheese("FAIL"); | |
} | |
private Builder getCorrectlyFilledBuilder() { | |
return pizzaOrder(10, "tomato", "Sander").withPepperoni().withMushroom() | |
.withCheese("parmesan"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment