I've been working on refactoring some code, and I think I may have taken the first step down the rabbit hole. I'm writing the example in Java, but I suppose it could be agnostic.
I have an interface Foo
defined as
public interface Foo {
int getX();
int getY();
int getZ();
}
And an implementation as
public final class DefaultFoo implements Foo {
public DefaultFoo(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
private final int x;
private final int y;
private final int z;
}
I also have an interface MutableFoo
that provides matching mutators
/**
* This class extends Foo, because a 'write-only' instance should not
* be possible and a bit counter-intuitive.
*/
public interface MutableFoo extends Foo {
void setX(int newX);
void setY(int newY);
void setZ(int newZ);
}
There are a couple of implementations of MutableFoo
that could exist (I haven't implemented them yet). One of them is
public final class DefaultMutableFoo implements MutableFoo {
/**
* A DefaultMutableFoo is not conceptually constructed
* without all values being set.
*/
public DefaultMutableFoo(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int getX() {
return x;
}
public void setX(int newX) {
this.x = newX;
}
public int getY() {
return y;
}
public void setY(int newY) {
this.y = newY;
}
public int getZ() {
return z;
}
public void setZ(int newZ) {
this.z = newZ;
}
private int x;
private int y;
private int z;
}
The reason I have split these is because it is equally likely for each to be used. Meaning, it is equally likely that someone using these classes will want an immutable instance, as it is they will want a mutable one.
The primary use-case that I have is an interface called StatSet
that represents certain combat details for a game (hitpoints, attack, defense). However, the "effective" stats, or the actual stats, are a result of the base stats, that can never be changed, and the trained stats, which can be increased. These two are related by
/**
* The EffectiveStats can never be modified independently of either the baseStats
* or trained stats. As such, this StatSet must never provide mutators.
*/
public StatSet calculateEffectiveStats() {
int effectiveHitpoints =
baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
int effectiveAttack =
baseStats.getAttack() + (trainedStats.getAttack() / 4);
int effectiveDefense =
baseStats.getDefense() + (trainedStats.getDefense() / 4);
return StatSetFactory.createImmutableStatSet(effectiveHitpoints, effectiveAttack, effectiveDefense);
}
the trainedStats are increased after every battle like
public void grantExperience() {
int hitpointsReward = 0;
int attackReward = 0;
int defenseReward = 0;
final StatSet enemyStats = enemy.getEffectiveStats();
final StatSet currentStats = player.getEffectiveStats();
if (enemyStats.getHitpoints() >= currentStats.getHitpoints()) {
hitpointsReward++;
}
if (enemyStats.getAttack() >= currentStats.getAttack()) {
attackReward++;
}
if (enemyStats.getDefense() >= currentStats.getDefense()) {
defenseReward++;
}
final MutableStatSet trainedStats = player.getTrainedStats();
trainedStats.increaseHitpoints(hitpointsReward);
trainedStats.increaseAttack(attackReward);
trainedStats.increaseDefense(defenseReward);
}
but they aren't increased just after battle. Using certain items, employing certain tactics, clever use of the battlefield all can grant different experience.
Now for my questions:
- Is there a name for splitting interfaces by accessors and mutators into separate interfaces?
- Is splitting them in this way the 'right' approach if they are equally likely to be used, or is there a different, more accepted pattern that I should use instead (eg.
Foo foo = FooFactory.createImmutableFoo();
which could returnDefaultFoo
orDefaultMutableFoo
but is hidden becausecreateImmutableFoo
returnsFoo
)? - Are there any immediately foreseeable downsides to using this pattern, save for complicating the interface hierarchy?
The reason I started designing it this way is because I'm of the mindset that all implementers of an interface should adhere to the simplest interface possible, and provide nothing more. By adding setters to the interface, now the effective stats can be modified independently of its parts.
Making a new class for the EffectiveStatSet
does not make much sense as we're not extending the functionality in any way. We could change the implementation, and make an EffectiveStatSet
a composite of two different StatSets
, but I feel that is not the right solution;
public class EffectiveStatSet implements StatSet {
public EffectiveStatSet(StatSet baseStats, StatSet trainedStats) {
// ...
}
public int getHitpoints() {
return baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
}
}