Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java Builders: Why, when and how?

Java Builders: Why, when and how?

Lightning talk presented to Manchester Java Community about different types of Java builders and how to best work with them.

Mark Crossfield

September 28, 2017
Tweet

More Decks by Mark Crossfield

Other Decks in Technology

Transcript

  1. Structure • What do I mean by builder? • Level

    1: Handling Default Values • Level 2: Writing Better Tests • Level 3: Building Better APIs 3
  2. // JavaBeans Pattern - allows inconsistency, mandates mutability public class

    NutritionFacts { // Parameters initialized to default values (if any) private int servingSize = -1; // Required; no default value private int servings = -1; // " " " " private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public NutritionFacts() { } // Setters public void setServingSize(int val) { servingSize = val; } public void setServings(int val) { servings = val; } public void setCalories(int val) { calories = val; } public void setFat(int val) { fat = val; } public void setSodium(int val) { sodium = val; } public void setCarbohydrate(int val) { carbohydrate = val; } } 9
  3. “Mutable shared state is the root of all evil in

    concurrent systems” —Dale Schumacher, 2011
  4. // Telescoping constructor pattern - does not scale well! public

    class NutritionFacts { private final int servingSize; // (mL) required private final int servings; // (per container) required private final int calories; // optional private final int fat; // (g) optional private final int sodium; // (mg) optional private final int carbohydrate; // (g) optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; } } 14
  5. Drawbacks Does not scale to many fields Suffers from primitive

    obsession Hard to handle combinations of defaults 16
  6. // Builder Pattern public class NutritionFacts { … public static

    class Builder { // Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int carbohydrate = 0; private int sodium = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } … } 18
  7. // Builder Pattern public class NutritionFacts { private final int

    servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { … } private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } } 19
  8. package javabuildersdemo; … public class CarTest { @Test public void

    equality() { assertThat(new Car("ford", "fiesta", "2005", "automatic", "red", "PK03MLJ")) .as("Check equality") .isEqualTo(new Car("ford", "fiesta", "2005", "automatic", "red", "PK03MLJ")); assertThat(testCar().build()) .as("Check equality") .isEqualTo(testCar().build()); } @Test public void colorEquality() { assertThat(new Car("ford", "fiesta", "2005", "automatic", "red", "PK03MLJ")) .as("Check color effects equality") .isNotEqualTo(new Car("ford", "fiesta", "2005", "automatic", "blue", "PK03MLJ")); assertThat(testCar().withColor("red").build()) .as("Check color effects equality") .isNotEqualTo(testCar().withColor("blue").build()); } @Test public void transmissionEquality() { assertThat(new Car("ford", "fiesta", "2005", "automatic", "red", "PK03MLJ")) .as("Check transmission effects equality") .isNotEqualTo(new Car("ford", "fiesta", "2005", "manual", "red", "PK03MLJ")); assertThat(testCar().withAutomaticTransmission().build()) .as("Check transmission effects equality") .isNotEqualTo(testCar().withManualTransmission().build()); } } 25
  9. Recap • What do I mean by builder? • Level

    1: Handling Default Values Immutability Consistent state Handling combinations of defaults • Level 2: Writing Better Tests Immutability Consistent state Less noise Many objects from one builder instance • Level 3: Building Better APIs Avoiding non-valid choices 31