Al momento sto lavorando a un gioco di Pokemon e sto incontrando alcuni problemi di progettazione. L'esempio più semplice è il seguente:
Ogni Species
di Pokemon ha diversi tratti che sono richiesti prima che sia inizializzato logicamente. Questi includono, ma non sono limitati a:
-
id
: una rappresentazione inglese diname
(in genere solo il nome in lettere minuscole). Esempio:charizard
-
name
: il nome localizzato diSpecies
. Esempio:Charizard
in inglese,Rizardon
in giapponese eDracaufeu
in francese. -
primaryType
: l'elemento principale di questoSpecies
. Esempio: il tipo principale diCharizard
èFire
perché è un drago sputafuoco. -
secondaryType
: (facoltativo) ogniSpecies
può anche avere un tipo secondario. Esempio: il tipo secondario diCharizard
èFlying
, ma la pre-evoluzione non ha ali, quindi non hasecondaryType
.null
va bene qui. -
baseStats
: le abilità di combattimento di base di tutti i membri di unSpecies
-
evPointYield
: il tipo di esperienza acquisita dopo aver sconfitto un membro diSpecies
Per brevità, mi fermo qui. Ci sono almeno altri 4 campi che sono richiesti, ma non sono importanti per l'esempio.
In questo esempio, poiché tutti i campi sono obbligatori prima che venga creato un Species
(leggi: nessun Species
può esistere senza tutti questi campi), un costruttore avrà un aspetto simile a
public Species(final String id, final String name, final Type primaryType,
final Type secondaryType, final StatSet baseStats, final StatSet evPointYield) {
// argument checking omitted for brevity
this.id = id;
this.name = name;
this.primaryType = primaryType;
this.secondaryType = secondaryType;
this.baseStats = baseStats;
this.evPointYield = evPointYield;
}
Non c'è modo di raggrupparli logicamente in oggetti diversi per ridurre la quantità di parametri passati nel costruttore.
Il mio primo tentativo di soluzione è stato creare un Builder
public class Species {
public static final class Builder {
// setters for all 6 fields
public Pokemon build() {
return new Pokemon(this);
}
}
public Species(final Species.Builder builder) {
// ensure builder is not null
// set fields from builder
}
}
Ma questo dovrebbe essere fatto da tutti gli utenti, mentre non chiama nessun metodo come
Species species = new Species.Builder().build();
Quindi, ho modificato il metodo di build per includere un metodo di convalida
public Species build() {
if (!stateIsValid()) {
// actual message is more descriptive, including which specific fields
// were missed
throw new IllegalStateException("not all fields were initialized before building");
}
return new Species(this);
}
private boolean stateIsValid() {
return
idIsValid() &&
nameIsValid() &&
primaryTypeIsValid() &&
secondaryTypeIsValid() &&
baseStatsAreValid() &&
evPointYieldIsValid();
}
private boolean idIsValid() {
// return it was set to a valid value
}
// other state checkers
Dovrei anche notare che sto usando Species
come più di un tipo di dati. Significa che i campi saranno caricati da da qualche parte e ci sono pochissime operazioni logiche che sarebbero necessarie in questa classe. Questa classe (sh / w) non può essere utilizzata per uno specifico Species
come:
public class Charizard extends Species {
}
ma piuttosto per aggiungere funzionalità aggiuntive a tutti Species
public class ComparableSpecies extends Species implements Comparable {
// ...
}
Questo accade altre volte nel codice, dove un oggetto non è costruito logicamente finché tutti i campi non sono impostati. Se ricordo male, un costruttore dovrebbe contenere tutte le informazioni necessarie per completare logicamente la costruzione di un oggetto, ma cosa succede se questo comporta un sacco di dati (come in questo esempio)?
Quindi alla domanda: un Builder
sarebbe l'approccio giusto per questo problema? C'è un altro modello che sarebbe più adatto per questo tipo di costruzione? O ho appena finito il Builder
tana del coniglio così lontano che non riesco a vedere cosa c'è di giusto in faccia?
(Nota: non preoccuparti / concentrati su / su come le modifiche alle classi avranno un impatto su tutti i consumatori a valle della mia API. Non ci sono ancora consumatori, dato che tutto il codice è ancora privato, e nemmeno pronto per alpha test ancora)
Qualsiasi feedback sarebbe apprezzato.