Skip to content

Commit 5dad7af

Browse files
author
Nicolai Parlog
committed
[02] Demonstrate builders and show alternatives
1 parent 09c4e1d commit 5dad7af

File tree

15 files changed

+769
-0
lines changed

15 files changed

+769
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ Related links:
1414

1515
* Item 1: [Consider static factory methods instead of constructors](https://www.youtube.com/watch?v=WUROOKn2OTk) -
1616
[examples](src/main/java/org/codefx/demo/effective_java/_01_static_factory_methods/Main.java)
17+
* Item 2: [Consider a builder when faced with many constructor parameters](https://www.youtube.com/watch?v=2GMp8VuxZnw)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.codefx.demo.effective_java._02_builder_pattern;
2+
3+
import org.codefx.demo.effective_java._02_builder_pattern.SelfTypes.Employee;
4+
import org.codefx.demo.effective_java._02_builder_pattern.SelfTypes.EmployeeBuilder;
5+
import org.codefx.demo.effective_java._02_builder_pattern.SelfTypes.Freelancer;
6+
import org.codefx.demo.effective_java._02_builder_pattern.SelfTypes.FreelancerBuilder;
7+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.Body;
8+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.Car;
9+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.CarFactory;
10+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.ConvertibleBody;
11+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.Decal;
12+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.Paint;
13+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.RearSpoiler;
14+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.RoofSpoiler;
15+
import org.codefx.demo.effective_java._02_builder_pattern.TowardsDsl.Tires;
16+
17+
/*
18+
* You will find NOTEs on the following topics:
19+
* - alternatives
20+
* - DSL
21+
* - self type
22+
* - refactoring
23+
*/
24+
25+
public class Main {
26+
27+
public static void main(String[] args) {
28+
towardsDsl();
29+
selfType();
30+
}
31+
32+
private static void towardsDsl() {
33+
Car car = new CarFactory()
34+
.body(new Body())
35+
.spoiler(new RoofSpoiler())
36+
.paint(new Paint())
37+
.decal(new Decal())
38+
.tires(new Tires())
39+
.build();
40+
41+
Car convertible = new CarFactory()
42+
.body(new ConvertibleBody())
43+
// can't add a roof spoiler!
44+
.spoiler(new RearSpoiler())
45+
.paint(new Paint())
46+
.decal(new Decal())
47+
.tires(new Tires())
48+
.build();
49+
}
50+
51+
private static void selfType() {
52+
Employee john = new EmployeeBuilder()
53+
.name("John Doe")
54+
.address("Paris")
55+
.build();
56+
Freelancer jane = new FreelancerBuilder()
57+
.name("Jane Doe")
58+
.address("Paris")
59+
.build();
60+
}
61+
62+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
package org.codefx.demo.effective_java._02_builder_pattern
3+
4+
class NutritionFacts(
5+
val servingSize: Int,
6+
val servings: Int,
7+
val fat: Int = 0,
8+
val sodium: Int = 0,
9+
val carbohydrates: Int = 0) { }
10+
11+
fun main(args: Array<String>) {
12+
val facts = NutritionFacts(
13+
servingSize = 150, servings = 4,
14+
fat = 20, carbohydrates = 25);
15+
}
16+
17+
*/
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.codefx.demo.effective_java._02_builder_pattern;
2+
3+
// NOTE refactoring: Your IDE should help you refactor from constructor to builder.
4+
public class RefactorToBuilder {
5+
6+
public static void main(String[] args) {
7+
Person p = new Person("Jane Doe");
8+
System.out.println(p);
9+
}
10+
11+
public static class Person {
12+
13+
private final String name;
14+
15+
public Person(String name) {
16+
this.name = name;
17+
}
18+
19+
}
20+
21+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package org.codefx.demo.effective_java._02_builder_pattern;
2+
3+
public class SelfTypes {
4+
5+
public abstract static class Person {
6+
7+
private final String name;
8+
private final String address;
9+
10+
protected Person(PersonBuilder<?, ?> builder) {
11+
this.name = builder.name;
12+
this.address = builder.address;
13+
}
14+
15+
public String name() {
16+
return name;
17+
}
18+
19+
public String address() {
20+
return address;
21+
}
22+
23+
}
24+
25+
public static class Employee extends Person {
26+
27+
private Employee(EmployeeBuilder builder) {
28+
super(builder);
29+
}
30+
31+
}
32+
33+
public static class Freelancer extends Person {
34+
35+
private Freelancer(FreelancerBuilder builder) {
36+
super(builder);
37+
}
38+
39+
}
40+
41+
// NOTE self type:
42+
// With a recursive generic type `SELF extends PersonBuilder<SELF>` (ignoring `P` for a moment,
43+
// which does not pertain to this), it is possible to capture "type of this instance".
44+
public static abstract class PersonBuilder<P extends Person, SELF extends PersonBuilder<P, SELF>> {
45+
46+
protected String name;
47+
protected String address;
48+
49+
// NOTE self type:
50+
// Thanks to `SELF`, `name(String)` can express that it returns not just a `PersonBuilder`,
51+
// but an instance of the concrete builder it was called on.
52+
public SELF name(String name) {
53+
this.name = name;
54+
// NOTE self type:
55+
// This cast is safe, so I find it tolerable. See below for an alternative.
56+
return (SELF) this;
57+
}
58+
59+
// NOTE self type:
60+
// To avoid the casts `(SELF) this`, call this method instead.
61+
// protected abstract SELF self();
62+
63+
public SELF address(String address) {
64+
this.address = address;
65+
return (SELF) this;
66+
}
67+
68+
public abstract P build();
69+
70+
}
71+
72+
public static class EmployeeBuilder extends PersonBuilder<Employee, EmployeeBuilder> {
73+
74+
@Override
75+
public Employee build() {
76+
return new Employee(this);
77+
}
78+
79+
// NOTE self type:
80+
// Here's how to implement `self()` in a concrete builder.
81+
// @Override
82+
// protected EmployeeBuilder self() {
83+
// return this;
84+
// }
85+
86+
}
87+
88+
public static class FreelancerBuilder extends PersonBuilder<Freelancer, FreelancerBuilder> {
89+
90+
@Override
91+
public Freelancer build() {
92+
return new Freelancer(this);
93+
}
94+
95+
}
96+
97+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package org.codefx.demo.effective_java._02_builder_pattern;
2+
3+
// NOTE DSL:
4+
// The common approach to a builder is to have a single class, on which `build()` can be called
5+
// at any time. If the provided information was incomplete, a `RuntimeException` is the result.
6+
//
7+
// The `CarFactory` in this class follows a more powerful/complex approach where each required
8+
// "setter" transitions to a new type of builder and only the last one has a `build()` method.
9+
// This way, other constructors/methods can use types for their parameters that represent a
10+
// specific "phase" of the build process. The progression from empty to complete builder is thus
11+
// encoded in the type system, which means the compiler can check errors that would otherwise
12+
// only be discovered at run time.
13+
public class TowardsDsl {
14+
15+
public static class Car {
16+
17+
private final Body body;
18+
private final Spoiler spoiler;
19+
private final Paint paint;
20+
private final Decal decal;
21+
private final Tires tires;
22+
23+
private Car(WithPaintedBodyAndTires builder) {
24+
this.body = builder.body;
25+
this.spoiler = builder.spoiler;
26+
this.paint = builder.paint;
27+
this.decal = builder.decal;
28+
this.tires = builder.tires;
29+
}
30+
31+
}
32+
33+
public static class Body { }
34+
public static class ConvertibleBody extends Body { }
35+
36+
public static class Spoiler { }
37+
public static class RoofSpoiler extends Spoiler { }
38+
public static class RearSpoiler extends Spoiler { }
39+
40+
public static class Paint { }
41+
42+
public static class Decal { }
43+
44+
public static class Tires { }
45+
46+
public static class CarFactory {
47+
48+
// NOTE DSL: This method returns `WithBody` instead of a `CarFactory`.
49+
public WithBody body(Body body) {
50+
return new WithBody(body);
51+
}
52+
53+
public WithConvertibleBody body(ConvertibleBody body) {
54+
return new WithConvertibleBody(body);
55+
}
56+
57+
// NOTE self type:
58+
// There is no `build()` method here, because without body, the car is incomplete.
59+
60+
}
61+
62+
public static class WithBody {
63+
64+
private final Body body;
65+
private Spoiler spoiler;
66+
67+
public WithBody(Body body) {
68+
this.body = body;
69+
}
70+
71+
public WithBody spoiler(Spoiler spoiler) {
72+
this.spoiler = spoiler;
73+
return this;
74+
}
75+
76+
public WithPaintedBody paint(Paint paint) {
77+
return new WithPaintedBody(body, spoiler, paint);
78+
}
79+
80+
}
81+
82+
public static class WithConvertibleBody {
83+
84+
private final ConvertibleBody body;
85+
private RearSpoiler spoiler;
86+
87+
public WithConvertibleBody(ConvertibleBody body) {
88+
this.body = body;
89+
}
90+
91+
public WithConvertibleBody spoiler(RearSpoiler spoiler) {
92+
this.spoiler = spoiler;
93+
return this;
94+
}
95+
96+
public WithPaintedBody paint(Paint paint) {
97+
return new WithPaintedBody(body, spoiler, paint);
98+
}
99+
100+
}
101+
102+
public static class WithPaintedBody {
103+
104+
private final Body body;
105+
private final Spoiler spoiler;
106+
private final Paint paint;
107+
private Decal decal;
108+
109+
public WithPaintedBody(Body body, Spoiler spoiler, Paint paint) {
110+
this.body = body;
111+
this.spoiler = spoiler;
112+
this.paint = paint;
113+
}
114+
115+
public WithPaintedBody decal(Decal decal) {
116+
this.decal = decal;
117+
return this;
118+
}
119+
120+
public WithPaintedBodyAndTires tires(Tires tires) {
121+
return new WithPaintedBodyAndTires(body, spoiler, paint, decal, tires);
122+
}
123+
124+
}
125+
126+
public static class WithPaintedBodyAndTires {
127+
128+
private final Body body;
129+
private final Spoiler spoiler;
130+
private final Paint paint;
131+
private final Decal decal;
132+
private final Tires tires;
133+
134+
public WithPaintedBodyAndTires(Body body, Spoiler spoiler, Paint paint, Decal decal, Tires tires) {
135+
this.body = body;
136+
this.spoiler = spoiler;
137+
this.paint = paint;
138+
this.tires = tires;
139+
this.decal = decal;
140+
}
141+
142+
public Car build() {
143+
return new Car(this);
144+
}
145+
146+
}
147+
148+
}

0 commit comments

Comments
 (0)