Started reading Effective Java (3rd edition) by Joshua Bloch
- 7 minsEffective Java
I realized how little did I know about Java
and software design and design patterns in general. I have a substantial base of knowledge so far but I am planning on building a much better and higher level acquaintance of the programming language of the web and programming in general.
My Github repo
I am implementing some patterns and learning useful skills in the meantime. The following link leads to my repo. I am sharing some code snippets here as well.
First snippets
The builder pattern
Let’s make a pizza! :D The builder pattern is recommended when there are 4 or more parameters in a class. It is antipattern to write a constructor with 1,2,3,4,…, N values. It is much simpler to create a builder that gives back an object of the class while setting the required parameters.
An advanced example can be a generic Pizza
class with a Builder
class inside it. It is considered useful to create the builder class outside the class we want to build but we are not doing so in this example:
package qbeer.github.io;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, MOZZARELLA, ANCHOVY, CORN, PINEAPPLE};
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
protected abstract T self(); // protected so it must be implemented in each class accourdingly
}
public Pizza(Builder<?> builder){
toppings = builder.toppings.clone();
}
}
We should go through this class line by line. Each Pizza
implementation will have a set of toppings and a constructor with a builder parameter that will use the topping set of the builder. The builder class is abstract static since it needs an implementation on its own in each class that extends the Pizza
class. This builder as all builders must implement the parameters of the class it will eventually build. By default, one can add toppings to a pizza and the returned type T
must correspond to the implemented Builder of that class, so that’s why it is generalized. An abstract build()
method is going to build each pizza subclass and a self()
function must return the implemented Builder
not the one here, therefore, we can’t just implement return this;
here.
Moving on to a HungarianStylePizza
we shall extend this class above.
package qbeer.github.io;
import java.util.Objects;
public class HungarianPizza extends Pizza {
private final boolean isWithPaprika;
private final boolean isWithSourCream;
public enum Thickness {THIN, MEDIUM, THICK}
private final Thickness thickness;
public static class Builder extends Pizza.Builder<Builder> {
private boolean isWithPaprika = false;
private boolean isWithSourCream = false;
private Thickness thickness = Thickness.THICK;
Builder withPaprika() {
this.isWithPaprika = true;
return this;
}
Builder withSourCream() {
this.isWithSourCream = true;
return this;
}
Builder thickness(Thickness thickness) {
this.thickness = Objects.requireNonNull(thickness);
return this;
}
@Override HungarianPizza build() {
return new HungarianPizza(this);
}
@Override protected Builder self() {
return this;
}
}
private HungarianPizza(Builder builder) {
super(builder);
this.isWithPaprika = builder.isWithPaprika;
this.isWithSourCream = builder.isWithSourCream;
this.thickness = builder.thickness;
}
@Override public String toString() {
return "HungarianPizza=[withPaprika=" + isWithPaprika + ",withSourCream=" + isWithSourCream +
",thickness=" + thickness.toString() + ",toppings=" + toppings.toString() + "]";
}
}
This subclass of the Pizza
class extends it by adding an enum Thickness
to it and boolean isWithSourCream, isWithPaprika
parameters. The parameters are private final
since they are set in the constructor by the builder and not modified in the future. Therefore we are not in need of getter and setter functions.
The builder extends the Pizza.Builder<T extends Builder<T>>
class where T
equals the builder class of this subclass. So for the self()
method, it is returned. It implements the fields of the class that it builds and sets default values to some of the parameters. The build()
method is overridden by returning a HungarianPizza
better known as kenyérlángos
. The seld()
method should return this
since we would like to acquire the builder method for this class and not any other.
In the private constructor of the extended pizza class, the super(builder);
class must be called first and after that, we can set the remaining fields as well with our extended builder class. I override the toString()
method as well to be able to better see the result I get by building a Hungarian style pizza.
package qbeer.github.io;
public class Main {
public static void main(String[] args) {
HungarianPizza hungarianPizza1 = new HungarianPizza.Builder()
.addTopping(Pizza.Topping.HAM)
.addTopping(Pizza.Topping.MUSHROOM)
.withPaprika()
.withSourCream()
.thickness(HungarianPizza.Thickness.MEDIUM)
.build();
HungarianPizza hungarianPizza2 = new HungarianPizza.Builder()
.addTopping(Pizza.Topping.MOZZARELLA)
.addTopping(Pizza.Topping.ANCHOVY)
.withPaprika()
.build();
System.out.println(hungarianPizza1);
System.out.println(hungarianPizza2);
}
}
This way it is way easier to build a pizza with a bunch of extra toppings and optional toppings such as paprika or sour cream. We are able to decide the thickness and later on, we can implement a size variable as well. This way the class and the builder should be modified together. The output of this example is:
HungarianPizza=[withPaprika=true,withSourCream=true,thickness=MEDIUM,toppings=[HAM, MUSHROOM]]
HungarianPizza=[withPaprika=true,withSourCream=false,thickness=THICK,toppings=[MOZZARELLA, ANCHOVY]]
This is the first blog post in this series, I hope I can get through this book fast and efficiently.