Come devo gestire configurazioni incompatibili con il pattern Builder?

8

Questo è motivato da questa risposta a una domanda separata .

Il modello di build viene utilizzato per semplificare l'inizializzazione complessa, in particolare con i parametri di inizializzazione facoltativi). Ma non so come gestire correttamente le configurazioni reciprocamente esclusive.

Ecco una classe Image . Image può essere inizializzato da un file o da una dimensione, ma non entrambi . Usare i costruttori per rafforzare questa mutua esclusione è ovvio quando la classe è abbastanza semplice:

public class Image
{
    public Image(Size size, Thing stuff, int range)
    {
    // ... initialize empty with size
    }

    public Image(string filename, Thing stuff, int range)
    {
        // ... initialize from file
    }
}

Ora supponiamo che Image sia effettivamente configurabile in modo che il pattern del builder sia utile, improvvisamente questo potrebbe essere possibile:

Image image = new ImageBuilder()
                  .setStuff(stuff)
                  .setRange(range)
                  .setSize(size)           // <----------  NOT
                  .setFilename(filename)   // <----------  COMPATIBLE
                  .build();

Questi problemi devono essere rilevati in fase di esecuzione piuttosto che in fase di compilazione, il che non è la cosa peggiore. Il problema è che il rilevamento coerente e completo di questi problemi all'interno della classe ImageBuilder potrebbe diventare complesso, soprattutto in termini di manutenzione.

Come devo gestire configurazioni incompatibili nel modello di build?

    
posta kdbanman 11.07.2015 - 19:38
fonte

1 risposta

11

Hai il tuo costruttore. Tuttavia, a questo punto hai bisogno di alcune interfacce.

Esiste un'interfaccia FileBuilder che definisce un sottoinsieme di metodi (non setSize ) e un'interfaccia SizeBuilder che definisce un altro sottoinsieme di metodi (non setFilename ). Potresti desiderare di avere un'interfaccia GenericBuilder che estenda FileBuilder e SizeBuilder - non è necessario anche se alcune persone potrebbero preferire questo approccio.

Il metodo setSize() restituisce un SizeBuilder. Il metodo setFilename() restituisce un FileBuilder.

ImageBuilder ha tutta la logica sia per setSize() che setFileName() . Tuttavia, il tipo di ritorno per questi specificherà l'interfaccia per sottoinsiemi appropriata.

class ImageBulder implements FileBuilder, SizeBuilder {
    ImageBuilder() {
        doInitThings;
    }

    ImageBuilder setStuff(Thing) {
        doStuff;
        return this;
    }

    ImageBuilder setRange(int range) {
        rangeStuff;
        return this;
    }

    SizeBuilder setSize(Size size) {
        stuff;
        return this;
    }

    FileBuilder setFilename(String filename) {
        otherStuff;
        return this;
    }

    Image build() {
        return new Image(...);
    }
}

Un particolare vantaggio qui è che una volta che hai un SizeBuilder, tutti i ritorni devono essere SizeBuilders. L'interfaccia per questo sembra:

interface SizeBuilder {
    SizeBuilder setRange(int range);
    SizeBuilder setSize(Size size);
    SizeBuilder setStuff(Thing stuff);
    Image build();
}

interface FileBuilder {
    FileBuilder setRange(int range);
    FileBuilder setFilename(String filename);
    FileBuilder setStuff(Thing stuff);
    Image build();
}

Quindi, quando chiami uno di questi metodi, non sei più in grado di chiamare l'altro e creare un oggetto con uno stato non valido.

    
risposta data 11.07.2015 - 20:12
fonte

Leggi altre domande sui tag