Ho lavorato sul refactoring del codice, e penso di aver fatto il primo passo verso la tana del coniglio. Sto scrivendo l'esempio in Java, ma suppongo che potrebbe essere agnostico.
Ho un'interfaccia Foo
definita come
public interface Foo {
int getX();
int getY();
int getZ();
}
E un'implementazione come
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;
}
Ho anche un'interfaccia MutableFoo
che fornisce i mutatori corrispondenti
/**
* 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);
}
Ci sono un paio di implementazioni di MutableFoo
che potrebbero esistere (non le ho ancora implementate). Uno di questi è
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;
}
Il motivo per cui li ho divisi è perché è altrettanto probabile che ognuno venga usato. Significa, è altrettanto probabile che qualcuno che usa queste classi vorrà un'istanza immutabile, poiché ne vorranno uno mutabile.
Il caso d'uso principale che ho è un'interfaccia chiamata StatSet
che rappresenta alcuni dettagli di combattimento per un gioco (punti ferita, attacco, difesa). Tuttavia, le statistiche "effettive", o le statistiche effettive, sono il risultato delle statistiche di base, che non possono mai essere cambiate, e le statistiche addestrate, che possono essere aumentate. Questi due sono correlati da
/**
* 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);
}
gliStats addestrati vengono aumentati dopo ogni battaglia come
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);
}
ma non aumentano subito dopo la battaglia. Utilizzando determinati oggetti, utilizzando determinate tattiche, l'uso intelligente del campo di battaglia può garantire un'esperienza diversa.
Ora per le mie domande:
- Esiste un nome per suddividere le interfacce di accessori e mutatori in interfacce separate?
- È diviso in questo modo l'approccio "giusto" se è altrettanto probabile che venga utilizzato, oppure esiste un modello diverso e più accettato che dovrei usare al suo posto (ad esempio
Foo foo = FooFactory.createImmutableFoo();
che potrebbe restituireDefaultFoo
oDefaultMutableFoo
ma è nascosto perchécreateImmutableFoo
restituisceFoo
)? - Ci sono degli aspetti immediatamente prevedibili nell'usare questo modello, salvo complicare la gerarchia dell'interfaccia?
Il motivo per cui ho iniziato a progettarlo in questo modo è perché sono dell'idea che tutti gli implementatori di un'interfaccia dovrebbero aderire all'interfaccia più semplice possibile e non fornire nulla di più. Aggiungendo setter all'interfaccia, ora le statistiche efficaci possono essere modificate indipendentemente dalle sue parti.
Rendere una nuova classe per EffectiveStatSet
non ha molto senso perché non stiamo estendendo le funzionalità in alcun modo. Potremmo modificare l'implementazione e creare un EffectiveStatSet
di un composito di due diversi StatSets
, ma ritengo che non sia la soluzione giusta;
public class EffectiveStatSet implements StatSet {
public EffectiveStatSet(StatSet baseStats, StatSet trainedStats) {
// ...
}
public int getHitpoints() {
return baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
}
}