He estado trabajando en la refactorización de algún código, y creo que podría haber dado el primer paso por la madriguera del conejo. Estoy escribiendo el ejemplo en Java, pero supongo que podría ser agnóstico.
Tengo una interfaz Foo
definida como
public interface Foo {
int getX();
int getY();
int getZ();
}
Y una implementación como
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;
}
También tengo una interfaz MutableFoo
que proporciona mutadores coincidentes
/**
* 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);
}
Hay un par de implementaciones MutableFoo
que podrían existir (aún no las he implementado). Uno de ellos es
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;
}
La razón por la que los he dividido es porque es igualmente probable que se use cada uno. Es decir, es igualmente probable que alguien que use estas clases quiera una instancia inmutable, ya que querrá una mutable.
El caso de uso principal que tengo es una interfaz llamada StatSet
que representa ciertos detalles de combate para un juego (puntos de vida, ataque, defensa). Sin embargo, las estadísticas "efectivas", o las estadísticas reales, son el resultado de las estadísticas base, que nunca se pueden cambiar, y las estadísticas entrenadas, que se pueden aumentar. Estos dos están relacionados por
/**
* 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);
}
los Stats entrenados se incrementan después de cada batalla como
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);
}
pero no aumentan justo después de la batalla. El uso de ciertos elementos, el empleo de ciertas tácticas, el uso inteligente del campo de batalla pueden otorgar una experiencia diferente.
Ahora para mis preguntas:
- ¿Existe un nombre para dividir las interfaces por accesores y mutadores en interfaces separadas?
- ¿Es dividirlos de esta manera el enfoque 'correcto' si es igualmente probable que se usen, o hay un patrón diferente y más aceptado que debería usar en su lugar (por ejemplo,
Foo foo = FooFactory.createImmutableFoo();
que podría regresarDefaultFoo
oDefaultMutableFoo
está oculto porquecreateImmutableFoo
regresaFoo
)? - ¿Hay alguna desventaja previsible de inmediato al usar este patrón, salvo por complicar la jerarquía de la interfaz?
La razón por la que comencé a diseñarlo de esta manera es porque tengo la mentalidad de que todos los implementadores de una interfaz deben adherirse a la interfaz más simple posible y no proporcionar nada más. Al agregar setters a la interfaz, ahora las estadísticas efectivas se pueden modificar independientemente de sus partes.
Crear una nueva clase para el EffectiveStatSet
no tiene mucho sentido ya que no estamos ampliando la funcionalidad de ninguna manera. Podríamos cambiar la implementación y hacer un EffectiveStatSet
compuesto de dos diferentes StatSets
, pero creo que esa no es la solución correcta;
public class EffectiveStatSet implements StatSet {
public EffectiveStatSet(StatSet baseStats, StatSet trainedStats) {
// ...
}
public int getHitpoints() {
return baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
}
}