I have the following class hierarchy:
Stuff
/ \
Food Drink
| |
Pizza Juice
/ \ / \
| SicilianPizza | OrangeJuice
| AppleJuice
CaliforniaPizza
Each class has a field(s) that should be set, some of those fields are obligatory (ob) and must be passed, some are optional (op) and can be set via a builder:
Stuff -> double price (op)
Food -> String mealType (op)
Pizza -> List<String> toppings (op), int size (ob)
CaliforniaPizza -> boolean addOlives (op)
SicilianPizza -> boolean addCheese (op)
Drink -> double density (ob), double alcoholVolume (op)
Juice -> String color (op)
AppleJuice -> String appleColor (op)
OrangeJuice -> int orangeSize (op)
I use builders to build the pizzas and drinks:
TestStuff.java:
public class TestStuff {
public static void main(String[] args) {
CaliforniaPizza californiaPizze = CaliforniaPizza.builder(2)
.addTopping("Tomatoes").addOlives(true).build();
SicilianPizza sicilianPizza = SicilianPizza.builder(1)
.addTopping("Bacon").addCheese(false).build();
AppleJuice appleJuice = AppleJuice.builder(40)
.setPrice(120).setAlcoholVolume(0).setAppleColor("yellow").build();
OrangeJuice orangeJuice = OrangeJuice.builder(35).setOrangeSize(8).build();
}
}
These are my classes:
Stuff.java:
public abstract class Stuff {
protected double price;
protected Stuff() {}
protected abstract class Builder<T extends Builder> {
protected abstract Stuff build();
protected abstract T self();
public T setPrice(double price) {
Stuff.this.price = price;
return (T) self();
}
}
}
Food.java:
public abstract class Food extends Stuff {
protected String mealType; //breakfast/dinner/etc
protected Food() {}
public abstract class Builder<T extends Builder> extends Stuff.Builder<Builder> {
protected abstract Food build();
protected abstract T self();
public T setMealType(String mealType) {
Food.this.mealType = mealType;
return (T) self();
}
}
}
Pizza.java:
public abstract class Pizza extends Food {
protected List<String> toppings = new ArrayList<>(); //optional
protected int size; //obligatory
protected Pizza(int size) {this.size = size;}
public abstract class Builder<T extends Builder> extends Food.Builder<Builder> {
public T addTopping(String topping) {
toppings.add(topping);
return (T) self();
}
}
}
CaliforniaPizza.java:
public class CaliforniaPizza extends Pizza {
private boolean addOlives;
private CaliforniaPizza(int size) {super(size);}
public static Builder builder(int size) {return new CaliforniaPizza(size).new Builder();}
public class Builder extends Pizza.Builder<Builder> {
@Override
public CaliforniaPizza build() {
return CaliforniaPizza.this;
}
@Override
public Builder self() {return this;}
public Builder addOlives(boolean addOlives) {
CaliforniaPizza.this.addOlives = addOlives;
return this;
}
}
}
SicilianPizza.java:
public class SicilianPizza extends Pizza {
private boolean addCheese;
private SicilianPizza(int size) {super(size);}
public static Builder builder(int size) {
return new SicilianPizza(size).new Builder();
}
public class Builder extends Pizza.Builder<Builder> {
@Override
public SicilianPizza build() {return SicilianPizza.this;}
@Override
public Builder self() {return this;}
public Builder addCheese(boolean addCheese) {
SicilianPizza.this.addCheese = addCheese;
return this;
}
}
}
Drink.java:
public abstract class Drink extends Stuff {
protected double density;
protected double alcoholVolume;
protected Drink(double density) {this.density = density;}
public abstract class Builder<T extends Builder> extends Stuff.Builder<Builder> {
protected abstract Drink build();
protected abstract T self();
public T setAlcoholVolume(double alcoholVolume) {
Drink.this.alcoholVolume = alcoholVolume;
return (T) self();
}
}
}
Juice.java:
public abstract class Juice extends Drink {
private String color;
protected Juice(double density) {super(density);}
public abstract class Builder<T extends Builder> extends Drink.Builder<Builder> {
public Builder setColor(String color) {
Juice.this.color = color;
return (T) self();
}
}
}
AppleJuice.java:
public class AppleJuice extends Juice {
private String appleColor;
private AppleJuice(double density) {super(density);}
public static Builder builder(double density) {return new AppleJuice(density).new Builder();}
public class Builder extends Juice.Builder<Builder> {
@Override
public AppleJuice build() {
return AppleJuice.this;
}
@Override
public Builder self() {
return this;
}
public Builder setAppleColor(String appleColor) {
AppleJuice.this.appleColor = appleColor;
return this;
}
}
}
OrangeJuice.java:
public class OrangeJuice extends Juice{
private int orangeSize;
private OrangeJuice(double density) {super(density);}
public static Builder builder(double density) {return new OrangeJuice(density).new Builder();}
public class Builder extends Juice.Builder<Builder> {
@Override
public OrangeJuice build() {return OrangeJuice.this;}
@Override
public Builder self() {return this;}
public Builder setOrangeSize(int orangeSize) {
OrangeJuice.this.orangeSize = orangeSize;
return this;
}
}
}
I have several problems with this code:
@Override
annotations abovebuild()
andself
insideCaliforniaPizza
andSicilianPizza
are highlighted in Idea Intellij and the error message says that method does not override method from its super class. While there are no such errors insideJuice
child classes.- This line inside
TestStuff
:
AppleJuice appleJuice = AppleJuice.builder(40)
.setPrice(120).setAlcoholVolume(0)
.setAppleColor("yellow").build();
gives error: setAppleColor
is highlited with red and can't be resolved. The reason is that setAlcoholVolume
returns a Drink.Builder
instead of AppleJuice.Builder
. At the same time if I were to call setAlcoholVolume
right after builder()
method it would return Juice.Builder
:
- Although there are problems with juices builders, at least I can somewhat access all the methods (at least if I call them right after
builder()
. However with foods my options are limited:
There's not Food's setMealType
there.
- I have a
return (T) self()
line inside setters inJuice
andPizza
. However, only insideJuice
do I get the warning about unchecked cast:
while in Pizza
I get no warning at all.
I have suspicions that at least problems 2 and 3 have to do with type erasure. An AppleJuice.Builder
gets passed to Juice.Builder
as T
, but then the Juice.Builder<AppleJuice.Builder>
gets passed to Drink.Builder<Juice.Builder>
and the information about AppleJuice
gets lost. Then Drink.Builder<Juice.Builder>
gets passed to Stuff.Builder<Drink.Builder>
and only information about Drink.Builder
is retained. Thus when I call setPrice(120)
right away, it returns Drink.Builder
instead of Juice.Builder
. If I'm correct, what is the way to fix it? And what are the reasons behind another issues I've encountered?
Aucun commentaire:
Enregistrer un commentaire