Pracowałem nad refaktoryzacją kodu i myślę, że mogłem zrobić pierwszy krok w stronę króliczej nory. Piszę przykład w Javie, ale przypuszczam, że może być agnostyczny.
Mam interfejs Foo
zdefiniowany jako
public interface Foo {
int getX();
int getY();
int getZ();
}
I wdrożenie jako
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;
}
Mam również interfejs, MutableFoo
który zapewnia dopasowanie mutatorów
/**
* 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);
}
Istnieje kilka implementacji, MutableFoo
które mogą istnieć (jeszcze ich nie wdrożyłem). Jeden z nich jest
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;
}
Powodem, dla którego je podzieliłem, jest to, że użycie każdego z nich jest równie prawdopodobne. Oznacza to, że równie prawdopodobne jest, że ktoś korzystający z tych klas będzie chciał instancji niezmiennej, ponieważ tak właśnie będzie chciał mieć zmienną instancję.
Podstawowym przykładem użycia, który mam, jest nazywany interfejs, StatSet
który reprezentuje pewne szczegóły walki dla gry (punkty życia, atak, obrona). Jednak „skuteczne” statystyki lub rzeczywiste statystyki są wynikiem statystyk podstawowych, których nigdy nie można zmienić, oraz wyszkolonych statystyk, które można zwiększyć. Te dwa są powiązane przez
/**
* 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);
}
Trenowane stany są zwiększane po każdej bitwie
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);
}
ale nie są zwiększane tuż po bitwie. Używanie niektórych przedmiotów, stosowanie określonej taktyki, sprytne korzystanie z pola bitwy mogą zapewnić różne doświadczenia.
Teraz moje pytania:
- Czy istnieje nazwa dzielenia interfejsów przez akcesory i mutatory na osobne interfejsy?
- Czy podzielenie ich w ten sposób jest „właściwym” podejściem, jeśli są równie prawdopodobne, że zostaną użyte, czy też istnieje inny, bardziej akceptowany wzorzec, którego powinienem użyć (np.
Foo foo = FooFactory.createImmutableFoo();
Który mógłby zwrócićDefaultFoo
lubDefaultMutableFoo
ale jest ukryty, ponieważcreateImmutableFoo
zwracaFoo
)? - Czy są jakieś dające się natychmiast przewidzieć wady korzystania z tego wzorca, z wyjątkiem komplikowania hierarchii interfejsu?
Powodem, dla którego zacząłem projektować go w ten sposób, jest to, że uważam, że wszyscy realizatorzy interfejsu powinni stosować się do najprostszego możliwego interfejsu i nie dostarczać nic więcej. Dodając setery do interfejsu, teraz skuteczne statystyki można modyfikować niezależnie od jego części.
Stworzenie nowej klasy dla EffectiveStatSet
nie ma większego sensu, ponieważ w żaden sposób nie rozszerzamy funkcjonalności. Moglibyśmy zmienić implementację i stworzyć połączenie EffectiveStatSet
dwóch różnych StatSets
, ale uważam, że nie jest to właściwe rozwiązanie;
public class EffectiveStatSet implements StatSet {
public EffectiveStatSet(StatSet baseStats, StatSet trainedStats) {
// ...
}
public int getHitpoints() {
return baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
}
}