Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

I am working on a Pokemon game at the moment, and am running into some design co

ID: 639419 • Letter: I

Question

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 the name (usually just the name lowercase). Example: charizard
name: the localized name of the Species. Example: Charizard in English, Rizardon in Japanese, and Dracaufeu in French.
primaryType: the primary element of this Species. Example: Charizard's primary type is Fire because it is a fire-breathing dragon.
secondaryType: (optional) each Species may also have a secondary type. Example: Charizard's secondary type is Flying, but it's pre-evolution does not have wings, so it does not have a secondaryType. null is okay here.
baseStats: the base combat abilities of all members of a Species
evPointYield: the type of experience gained after defeating a member of a Species
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.

Explanation / Answer

The Builder pattern is most useful when the product you are building consists of multiple discrete parts, where external information specifies which parts, or if the product has many optional properties.

In your case, you have a long list of required properties. Here the Builder pattern only gives you the appearance of a less complex constructor at the cost of not being able to detect at compile-time that a property was missed.
You are essentially trading code like this

type1 property1 = ...
type2 property2 = ...
type3 property3 = ...
.
.
.
typeN propertyN = ...
Species x = new Species(property1, property2, property3, ..., propertyN);
for

Builder builder;
builder.setProperty1(...);
builder.setProperty3(...);
.
.
.
builder.setPropertyN(...);
Species x = builder.build();
The Species construction looks deliciously simple, but how long will it take you to see why this code doesn't work?

I know there are guidelines that methods should not take more than 3 (or 4) arguments. However, there is a reason why all languages in practical use don't impose that as a hard limit: Sometimes you just need more arguments. If even the most aggressive grouping of arguments into parameter objects can't get you lower than 6 or 7 parameters, then you just need that many.

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote