I am working on a Pokemon game at the moment, and am running into some design concerns. The easiest example is as follows:
Each Species
of Pokemon has several traits that are required before it is logically initialized. These include, but are not limited to:
id
: an English representation of thename
(usually just the name lowercase). Example:charizard
name
: the localized name of theSpecies
. Example:Charizard
in English,Rizardon
in Japanese, andDracaufeu
in French.primaryType
: the primary element of thisSpecies
. Example:Charizard
's primary type isFire
because it is a fire-breathing dragon.secondaryType
: (optional) eachSpecies
may also have a secondary type. Example:Charizard
's secondary type isFlying
, but it's pre-evolution does not have wings, so it does not have asecondaryType
.null
is okay here.baseStats
: the base combat abilities of all members of aSpecies
evPointYield
: the type of experience gained after defeating a member of aSpecies
For brevity, I'll stop there. There are at least 4 other fields that are required, but are not important for the example.
In this example, because all fields are required before a Species
is constructed (read: no Species
can possibly exist without all of these fields), a constructor would look something like
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;
}
There is no way to logically group these into different objects to reduce the amount of parameters passed into the constructor.
My first attempt at a solution was to make a 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
}
}
But this would all users to build, while not calling any methods like so
Species species = new Species.Builder().build();
So, I modified the build method to include a validation method
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
I should also note that I am using Species
as more of a data type. Meaning, that the fields will be loaded from somewhere, and there are very few logical operations that would be necessary in this class. This class (sh/w)ouldn't be used for specific Species
like:
public class Charizard extends Species {
}
but rather for adding additional functionality to all Species
public class ComparableSpecies extends Species implements Comparable {
// ...
}
This happens other times in the code, where an object isn't logically constructed until all fields are set. If I recall correctly, a constructor should contain all information necessary to logically complete construction of an object, but what happens if that involves a lot of data (like in this example)?
So to the question: Would a Builder
be the right approach for this problem? Is there another pattern that would be better suited for this type of construction? Or have I just gone down the Builder
rabbit hole so far that I can't see what's right in my face?
(Note: Don't worry/focus about/on how changes to the classes will impact any downstream consumers of my API. There are no consumers yet, as all of the code is still private, and not even ready for alpha testing yet)
Any feedback would be appreciated.
private Future<String> id
instead ofprivate String id
etc