How to extend immutable types in Java

Viewed 4309

I have started playing around with immutable value objects in Java while working on a game project, following the "public final fields" approach:

public class Team {
    public final String name, flag;

    public Team(String name, String flag) {
        this.name = name;
        this.flag = flag;
    }
}

This works pretty well for me so far, but I need different sets of extra information about the team in different circumstances. For example, a team has a set color during a match. The question is, what is the best way to deal with these sets of extended information? I know this is a fairly general question, but I want to keep using immutable objects and that might influence the solution.

Here are the options I have come up with. Most of them are probably "good enough", but I'd like to learn some arguments for and against them for future reference.

Option 1: Everything in one class

public class Team {
    public final String name, flag, colorName;
    public final int colorRgb;

    public Team(String name, String flag, String colorName, int colorRgb) {
        this.name = name;
        this.flag = flag;
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

This takes only one class for all uses, but there is no type-based indication of what extra data is expected/provided.

Option 2: Subclassing

public class TeamWithColor extends Team {
    public final String colorName;
    public final int colorRgb;

    public Team(String name, String flag, String colorName, int colorRgb) {
        super(name, flag);
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

This might make a content-based equals() implementation impossible.

Option 3: Composition

public class TeamWithColor {
    public final Team team;
    public final String colorName;
    public final int colorRgb;

    public Team(Team team, String colorName, int colorRgb) {
        this.team = team;
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

Less copying / boilerplate code if the team data and extra data often change independently.

Option 4: Pair/Tuple (using an immutable Pair class)

public class TeamColor {
    public final String colorName;
    public final int colorRgb;

    public Team(String colorName, int colorRgb) {
        this.colorName = colorName;
        this.colorRgb = colorRgb;
    }
}

Pair<Team, TeamColor> teamWithColor = Pair.create(team, teamColor);

... or with a custom class that ties Team and TeamColor together.

I tend toward option 3 or 4, but I'm interested in your opinions, arguments and gut feelings :)

6 Answers

I usually try to avoid putting everything in one class (option 1) this leaves us 4 options

  • in case I needed the class to be thread-safe I favor using a volatile/AtomicReference of an Immutable tuple (option 4) instead of using locks and other blocking syncs (let's call it: option 5)
  • if thread-safety isn't a concern then I would pick Composition (option 3) over In inheritance (option 2) unless an IS-A relation naturally exist

a Colored Team IS a Team after all, so I would be nice to exploit this relation. if I were you I would code it like this:

public final class ColoredTeam extends AbstractTeam implements Team {
    // the rest comes along intuitively
}

since "AbstractTeam" is abstract, content-based equals() implementation is perfectly fine as long "ColoredTeam" itself has no subclasses, I made ColoredTeam final to achieve this but I could have made the contractor private, throws an exception, etc.

Related