Reputation: 5176
I have this class structure:
Stuff
/ \
Food
|
FastFood Drink
| |
Pizza Juice
/ \ / \
| SicilianPizza | OrangeJuice
| AppleJuice
CaliforniaPizza
I can create pizza and juice objects using builders like this:
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();
Every setter method of the builder returns correct object:
I'd like to create a method that would accept a FastFood.Builder
object and use it to create a FastFood
object that would pass it to another method:
public void makeFat(FastFood fastFood) {...}
But I can't: setter methods don't return FastFood
, but rather its parents:
Is there something wrong with my generics? How could that be accomplished?
package pizza;
import java.util.*;
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();
}
public FastFood testFood(FastFood.Builder fastFoodBuilder) {
//return fastFoodBuilder.setMealType("fd").addOlives(true).setPrice(20).build();
}
public void makeFat(FastFood fastFood) {}
}
abstract class Stuff {
protected double price;
protected Stuff() {}
protected abstract class Builder<T extends Builder<T>> {
protected abstract Stuff build();
protected abstract T self();
public T setPrice(double price) {
Stuff.this.price = price;
return self();
}
}
}
abstract class Food extends Stuff {
protected String mealType; //breakfast/dinner/etc
protected Food() {}
public abstract class Builder<T extends Builder<T>> extends Stuff.Builder<T> {
protected abstract Food build();
protected abstract T self();
public T setMealType(String mealType) {
Food.this.mealType = mealType;
return self();
}
}
}
abstract class FastFood extends Food {
protected int harm;
protected FastFood() {}
public abstract class Builder<T extends Builder<T>> extends Food.Builder<T> {
protected abstract FastFood build();
protected abstract T self();
public T setHarm(int harm) {
FastFood.this.harm = harm;
return self();
}
}
}
abstract class Pizza extends FastFood {
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<T>> extends FastFood.Builder<T> {
public T addTopping(String topping) {
toppings.add(topping);
return self();
}
}
}
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;
}
}
}
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;
}
}
}
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<T>> extends Stuff.Builder<T> {
protected abstract Drink build();
protected abstract T self();
public T setAlcoholVolume(double alcoholVolume) {
Drink.this.alcoholVolume = alcoholVolume;
return self();
}
}
}
abstract class Juice extends Drink {
private String color;
protected Juice(double density) {super(density);}
public abstract class Builder<T extends Builder<T>> extends Drink.Builder<T> {
public Builder<T> setColor(String color) {
Juice.this.color = color;
return self();
}
}
}
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;
}
}
}
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;
}
}
}
Upvotes: 0
Views: 183
Reputation: 10315
There are few problems with the code.
Class FastFood.Builder
is generic: class Builder<T extends Builder<T>> extends Food.Builder<T>
.
In the method FastFood testFood(FastFood.Builder fastFoodBuilder)
the builder is declared without specifying the type T
.
For example, AppleJuice
specifies the parameter T
type: class Builder extends Juice.Builder<Builder>
.
The same problem will be present for Drink.Builder
:
public static Drink testDrink(Drink.Builder drinkBuilder) {
return drinkBuilder
.setPrice(100) // returns Stuff.Builder
.setAlcoholVolume(0.7) //Error: cannot resolve method
.build();
}
To fix this, make method testFood
generic accepting parameter FastFood.Builder<T>
:
public static <T extends FastFood.Builder<T>> FastFood testFood(FastFood.Builder<T> fastFoodBuilder) {
return fastFoodBuilder.setMealType("fd").setHarm(1).setPrice(20).build();
}
The same is true for methods accepting Drink.Builder
:
public static <T extends Drink.Builder<T>> Drink testDrink(Drink.Builder<T> drinkBuilder) {
return drinkBuilder.setPrice(100).setAlcoholVolume(0.7).build();
}
Usage example:
FastFood californiaPizza1 = testFood(CaliforniaPizza.builder(2));
In order to call addOlives(boolean)
method and avoid casting from FastFood
to a concrete class (e.g. CaliforniaPizza
), it's more convenient to return builder from a method:
public static <T extends FastFood.Builder<T>> T testFoodBuilder(FastFood.Builder<T> fastFoodBuilder) {
return fastFoodBuilder.setMealType("fd").setHarm(1).setPrice(20);
}
And the usage:
CaliforniaPizza californiaPizza2 =
testFoodBuilder(CaliforniaPizza.builder(2))
.addOlives(true)
.build();
Upvotes: 1
Reputation: 147164
You have the line:
public FastFood testFood(FastFood.Builder fastFoodBuilder) {
But also:
abstract class FastFood extends Food {
...
public abstract class Builder<T extends Builder<T>> extends Food.Builder<T> {
There should be a warning saying that FastFood.Builder
is a generic type.
The parameter type needs to be made appropriately generic. A wildcard will do (constraints will be inferred).
public FastFood testFood(FastFood.Builder<?> fastFoodBuilder) {
Upvotes: 1