In questa serie di post sul blog , Eric Lippert descrive un problema nella progettazione orientata agli oggetti usando maghi e guerrieri come esempi, dove:
abstract class Weapon { }
sealed class Staff : Weapon { }
sealed class Sword : Weapon { }
abstract class Player
{
public Weapon Weapon { get; set; }
}
sealed class Wizard : Player { }
sealed class Warrior : Player { }
e quindi aggiunge un paio di regole:
- Un guerriero può usare solo una spada.
- Un mago può usare solo uno staff.
Quindi continua a dimostrare i problemi che si incontrano se si tenta di applicare queste regole utilizzando il sistema di tipo C # (ad esempio, rendere la Wizard
classe responsabile per assicurarsi che un mago possa usare solo uno staff). Si viola il principio di sostituzione di Liskov, si rischiano eccezioni di runtime o si finisce con un codice che è difficile da estendere.
La soluzione che propone è che la classe Player non convalida. Viene utilizzato solo per tenere traccia dello stato. Quindi, invece di dare a un giocatore un'arma:
player.Weapon = new Sword();
lo stato è modificato da se Command
secondo Rule
s:
... creiamo un
Command
oggetto chiamatoWield
che prende due oggetti di stato del gioco, aPlayer
e aWeapon
. Quando l'utente invia un comando al sistema "questa procedura guidata dovrebbe brandire quella spada", quel comando viene valutato nel contesto di un insieme diRule
s, che produce una sequenza diEffect
s. Ne abbiamo unoRule
che dice che quando un giocatore tenta di impugnare un'arma, l'effetto è che l'arma esistente, se ce n'è una, viene lasciata cadere e la nuova arma diventa l'arma del giocatore. Abbiamo un'altra regola che rafforza la prima regola, che dice che gli effetti della prima regola non si applicano quando un mago cerca di impugnare una spada.
Mi piace questa idea in linea di principio, ma ho una preoccupazione su come potrebbe essere utilizzata nella pratica.
Nulla sembra impedire a uno sviluppatore di aggirare il Commands
e Rule
semplicemente impostando il parametro Weapon
a a Player
. La Weapon
proprietà deve essere accessibile dal Wield
comando, quindi non può essere effettuata private set
.
Allora, che cosa fa impedire che uno sviluppatore di fare questo? Devono solo ricordare di non farlo?