Eu tenho trabalhado na refatoração de algum código e acho que posso ter dado o primeiro passo na toca do coelho. Estou escrevendo o exemplo em Java, mas suponho que possa ser agnóstico.
Eu tenho uma interface Foo
definida como
public interface Foo {
int getX();
int getY();
int getZ();
}
E uma implementação 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;
}
Eu também tenho uma interface MutableFoo
que fornece mutadores correspondentes
/**
* 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);
}
Existem algumas implementações MutableFoo
que podem existir (ainda não as implementei). Uma delas é
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;
}
A razão pela qual eu os dividi é porque é igualmente provável que cada um seja usado. Ou seja, é igualmente provável que alguém que use essas classes deseje uma instância imutável, como é que eles desejem uma instância mutável.
O principal caso de uso que tenho é uma interface chamada StatSet
que representa certos detalhes de combate de um jogo (pontos de vida, ataque, defesa). No entanto, as estatísticas "efetivas", ou as estatísticas reais, são resultado das estatísticas básicas, que nunca podem ser alteradas, e das estatísticas treinadas, que podem ser aumentadas. Esses dois estão 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);
}
as estatísticas treinadas são aumentadas após cada batalha 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);
}
mas eles não aumentam logo após a batalha. Usando certos itens, empregando certas táticas, o uso inteligente do campo de batalha pode garantir uma experiência diferente.
Agora, para minhas perguntas:
- Existe um nome para dividir interfaces por acessadores e mutadores em interfaces separadas?
- Dividi-los dessa maneira é a abordagem 'correta', se é igualmente provável que sejam usados, ou existe um padrão diferente e mais aceito que eu deveria usar (por exemplo, o
Foo foo = FooFactory.createImmutableFoo();
que poderia retornarDefaultFoo
ou queDefaultMutableFoo
está oculto porquecreateImmutableFoo
retornaFoo
)? - Existem desvantagens imediatamente previsíveis no uso desse padrão, exceto por complicar a hierarquia da interface?
A razão pela qual comecei a projetá-lo dessa maneira é porque sou da opinião de que todos os implementadores de uma interface devem aderir à interface mais simples possível e não fornecem mais nada. Adicionando setters à interface, agora as estatísticas efetivas podem ser modificadas independentemente de suas partes.
Criar uma nova classe para o EffectiveStatSet
não faz muito sentido, pois não estamos estendendo a funcionalidade de forma alguma. Poderíamos mudar a implementação e formar EffectiveStatSet
um composto de dois diferentes StatSets
, mas acho que essa não é a solução certa;
public class EffectiveStatSet implements StatSet {
public EffectiveStatSet(StatSet baseStats, StatSet trainedStats) {
// ...
}
public int getHitpoints() {
return baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
}
}