È strano che un oggetto Builder abbia metodi getter?

4

Ho un tipo di dati immutabile piuttosto complesso che sto usando un oggetto costruttore per creare un'istanza. Attualmente, ho un setup in cui analizzo un file, settando vari campi nel mio builder e poi costruisco l'oggetto. Tuttavia, i file che sto usando sono soggetti a errori, quindi prima voglio fare qualche verifica sui miei oggetti non costruiti. Ed è importante notare che non voglio lanciare un'eccezione se un oggetto non supera la verifica, poiché ciò non significa che sia non valido , solo errato.

Il modo in cui sono abituato a vedere i costruttori, però, è che sono "di sola scrittura". Ad esempio, la classe ImmutableList.Builder di Guava non ti consente di visualizzare i dati che ti sono stati inviati, portandomi a utilizzare ArrayList s come builder in occasione della necessità di modificare i dati prima di bloccarli.

In alternativa, suppongo che potrei andare avanti e costruire i miei oggetti con errori, quindi creare un nuovo builder precaricato con i dati dell'oggetto costruito, ispezionare i dati, modificare e ricostruire. Questo sembra dispendioso e inelegante, però.

È considerato un odore di codice mettere i metodi getter sui tuoi oggetti builder? Intuitivamente, mi sembra un po 'strano, ma più di una volta mi sono trovato di fronte a questo tipo di problema, che spesso mi spinge a mantenere una copia accessibile di alcuni dei dati che sto passando al builder in modo che io possa guardalo più tardi Basta avere un getter per semplificare le cose.

    
posta codebreaker 15.05.2016 - 23:56
fonte

4 risposte

5

Penso che non ci sia nulla di sbagliato di per sé ad avere dei getter nella tua classe di builder che permettano di ispezionare quali dati sono stati passati. Come affermato da Robert Harvey, i getter non fanno parte del modello, ma a volte una soluzione pragmatica è meglio che attenersi a qualche dottrina. E non c'è "l'unico e solo modo corretto" per implementare un modello, il modello di progettazione ti dà sempre alcuni gradi di libertà su come implementarli.

Tuttavia, dovresti fare attenzione a non assegnare a molte diverse responsabilità quella classe (e se una classe di builder ha getter, questa potrebbe essere un'indicazione per questo). Se il tuo builder inizia a diventare un repository per i dati di input, un validatore per i dati e lo stesso builder, allora ha troppe responsabilità e dovresti considerare di suddividerlo in classi diverse.

    
risposta data 16.05.2016 - 00:16
fonte
1

Solleverei sicuramente un sopracciglio nei getter di un costruttore, e sembra una violazione del modello di responsabilità singola.

Sembra che tu abbia bisogno di un componente di convalida separato prima del costruttore. Quindi la tua catena di elaborazione dovrebbe leggere il file, convalidare i parametri e alimentare il costruttore. L'alternativa (e questa sarebbe una buona pratica in generale) è quella di codificare l'oggetto finale in modo tale che esegua post-condizioni al momento della costruzione, e genera un'eccezione se è stata alimentata con parametri errati. Questa è una buona pratica in generale dal momento che non si vuole essere in grado di costruire un tale oggetto in modo errato, costruttore o nessun costruttore.

    
risposta data 16.05.2016 - 10:09
fonte
0

Io farei i metodi factory nell'oggetto in cui i parametri non possono essere nulli. Ecco dove dovrebbe essere la valadizione. Quelli possono lanciare un execption.

Per quanto riguarda il costruttore, sostituirlo con un modello di fabbrica astratto e lasciare che lanci un execption. Per il caricamento, avere un elenco delle fabbriche. Prova a creare l'oggetto e se non è più null e non viene lanciata alcuna eccezione, restituisci gli oggetti.

Se è troppo lento, effettua il caricamento sul proprio thread e aggiungi a un elenco. Quindi fai passare il caricatore all'elenco a una classe che fa qualcosa.

    
risposta data 17.05.2016 - 18:52
fonte
0

Si consideri:

public abstract class Entity {
    public static abstract class Builder<T extends Entity, B extends Builder<T, B>> {
        private final T object;
        private final B thisBuilder;

        public Builder() {
            this.object = createObject();
            this.thisBuilder = thisBuilder();
        }

        protected abstract T createObject();
        protected abstract B thisBuilder();

        public T build() {
            return getObject();
        }

        protected T getObject() {
            return this.object;
        }
    }
}

La superclasse sopra per gli oggetti business contiene una classe Builder che fornisce un modo per utilizzare i dati all'interno degli oggetti business per inizializzarli, ma non espone necessariamente i dati tramite public accessors.

Ecco una classe che usa la classe Builder:

public final class Address extends Entity {
    private String city;

    protected synchronized String getCity() {
        return this.city == null ? "" : this.city;
    }

    protected void setCity(String city) {
        this.city = city;
    }

    public static final class Builder extends Entity.Builder<Address, Builder> {
        @Override
        protected Builder thisBuilder() {
            return this;
        }

        @Override
        protected Address createObject() {
            return new Address();
        }

        public Builder withCity(String city) {
            getObject().setCity(city);
            return thisBuilder();
        }
    }
}

Ora puoi creare un'istanza Address come segue:

Address address = new Address.Builder()
  .withCity( "Vancouver" )
  .build();

Nessun dato è stato esposto al di fuori del pacchetto poiché nessun accessor è esposto nella classe Builder. Inoltre, nessun dato è stato duplicato tra la classe Builder e l'oggetto creato (come mostrano alcuni altri modelli).

Uno svantaggio di questo approccio è la leggera sovrapposizione di funzionalità. Cioè, i metodi withXYZ del builder delegano il trasferimento dei dati tramite gli accessor setXYZ della classe da costruire.

L'ereditarietà senza doversi preoccupare del metodo di chiamata è possibile. Ad esempio:

public abstract class AbstractDate extends Entity {
    private Date date;

    protected synchronized Date getDate() {
        return this.date == null ? new Date() : this.date;
    }

    protected void setDate(Date date) {
        this.date = date;
    }

    protected static abstract class Builder
    <T extends AbstractDate, B extends Builder<T, B>> extends Entity.Builder<T, B> {
        public B withDate(int year, int month, int day) {
            getObject().setDate(createDate(year, month, day));
            return thisBuilder();
        }
    }
}

Inoltre, l'uso di un prefisso with per i nomi dei metodi può aiutare con il completamento automatico.

È strano avere metodi di accesso all'interno di un Builder? Piuttosto, forse, non sono necessari.

    
risposta data 17.05.2016 - 19:40
fonte

Leggi altre domande sui tag