Pattern di builder intelligente - diversi parametri a seconda del tipo - generici?

2

Diciamo che abbiamo il famoso Joshua Bloch Nutrition Builder e vogliamo cambiarlo in modo che sia un po 'come un generatore dinamico che limita la visibilità dei setter e utilizza genericamente i generici:

 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;
}

}

Ora vorrei che questo costruttore costruisse due tipi di NutritionFacts. Quindi diciamo invece di:

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

Mi piacerebbe avere qualcosa del genere:

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...

Non so se mi esprimo chiaro e se scrivessi i generici in luoghi appropriati ...

Consigli sui consigli, mostra alcuni dettagli, sto solo iniziando con i generici e lo adoro!

    
posta user1308908 21.01.2016 - 21:21
fonte

1 risposta

3

Credo che i generici non siano la soluzione giusta per il tuo problema. Le interfacce dovrebbero essere sufficienti.

In generale, i generici dovrebbero essere usati su qualcosa che può essere applicato a diversi tipi di oggetti generici . Qui, al contrario, vuoi un comportamento diverso (cioè l'interfaccia) dipendente dal tipo di oggetto.

Quando chiami new NutritionFacts.Builder<...>(240, 8) stai creando un Builder con un'interfaccia ben definita che nulla può cambiare.

Se utilizzi un metodo statico per ottenere il builder, puoi "mascherare" la sua interfaccia:

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

In questo modo puoi fare qualcosa di simile:

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

Questo richiede di creare un metodo statico per ogni tipo di builder necessario.

Proviamo generici

Ora puoi usare i generici se vuoi davvero, ma sarà brutto perché siamo al limite (o totalmente fuori?) di ciò a cui sono destinati:

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();
...

Perché è cattivo?

Questo è brutto a causa di quel cast non sicuro. La firma generica build() richiede solo che tu passi qualcosa che estenda IBuilder . Cosa succede se eseguo un'altra implementazione di IBuilder che non ha nulla a che fare con il nostro vecchio 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();
}

Controllare questo tipo di problemi diventa complicato, quindi non ne vale la pena. Invece con i semplici metodi statici, hai sempre sotto controllo i tuoi tipi.

    
risposta data 24.01.2016 - 12:27
fonte

Leggi altre domande sui tag