2

Lets say we have the famous Joshua Bloch Nutrition Builder and we want to change it so it be a bit like dynamic builder which restricts visibility of setters and propably uses generics :

 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 {
    // 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);
    }
}

private NutritionFacts(Builder builder) {
    servingSize  = builder.servingSize;
    servings     = builder.servings;
    calories     = builder.calories;
    fat          = builder.fat;
    sodium       = builder.sodium;
    carbohydrate = builder.carbohydrate;
}

}

Now I would like this builder to build two types of NutritionFacts. So lets say instead of:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
  calories(100).sodium(35).carbohydrate(27).build();

I would like to have something like that:

NutritionFactsA a = new NutritionFacts.Builder<NutritionFactsA>...(240, 8).
  calories(100).build(); //.sodium(35).carbohydrate(27) - these are not possible to be set, when I write NutritionFactsA I cannot see these parameters

NutritionFactsB b = new NutritionFacts.Builder<NutritionFactsB>(240, 8).
  .sodium(35).build(); //here I have set only sodium value, I cannot set anything else - I dont even see other setters...

I don't know if I express myself clear and if I wrote generics in proper places...

Pleas advice, show some details, I am only beginning with generics and love it!

3

I believe that generics are not the right solution to your problem. Interfaces should be enough.

In general, generics should be used on something that can be applied to different - generic - kinds of objects. Here, on the opposite, you want a different behaviour (i.e. interface) depending on the kind of object.

When you call new NutritionFacts.Builder<...>(240, 8) you're creating a Builder that has a well defined interface that nothing can change.

If you use a static method to get the builder you could "mask" its interface:

public class NutritionFacts {

    public interface IBuilder {
        NutritionFacts build();
    }

    public interface NutritionFactsABuilder extends IBuilder {
        NutritionFactsABuilder calories(int cal);
    }

    public interface NutritionFactsBBuilder extends IBuilder {
        NutritionFactsBBuilder sodium(int sodium);
    }

    // no more public
    static class Builder implements NutritionFactsABuilder, NutritionFactsBBuilder {
        // ...

        @Override
        public NutritionFactsABuilder calories(int val) { /*...*/ }

        @Override
        public NutritionFactsBBuilder sodium(int val) { /*...*/ }

        @Override
        public NutritionFacts build() { /*...*/ }
    }

    // this returns the builder for NutritionFactsA 
    public static NutritionFactsABuilder buildA(int x, int y) {
        return new Builder(x,y);
    }

    // this returns the builder for NutritionFactsB
    public static NutritionFactsBBuilder buildB(int x, int y) {
        return new Builder(x,y);
    }
}

This way you can do something like:

NutritionFacts.buildA(1,2) // now you can see only calories() and build()
        .calories(3)
        .build();

This requires you to create a static method for every kind of builder you need.

Let's try generics

Now you can use generics if you really want, but it's going to be ugly because we're on the edge (or totally outside?) of what they are meant for:

static public <T extends IBuilder> T build(Class<T> clazz, int x, int y) {
    return (T) new Builder(x,y);
}

// used like this:
...
NutritionFacts
        .build(NutritionFactsABuilder.class, 1, 2)
        .calories(2)
        .build();
...

Why is it bad?

This is ugly because of that unsafe cast. The generic build() signature only requires that you pass something that extends IBuilder. What if I make another implementation of IBuilder which has nothing to do with our old Builder?

public interface IPoisonBuilder extends IBuilder {
    IPoisonBuilder poison(int p);
}

public static class RogueBuilder implements IBuilder, IPoisonBuilder {

    @Override
    public IPoisonBuilder poison(int p) { /*...*/ }

    @Override
    public NutritionFacts build() { /*...*/ }
}

public static void main(String[] args) {
    NutritionFacts factsBad = NutritionFacts   // This compiles but...
            .build(IPoisonBuilder.class, 1, 3) // <-- ClassCastException at runtime!!!
            .poison(2)
            .build();
}

Checking on this kind of problems gets tricky, so it's not worth it. With the simple static methods instead, you always have your types under control.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.