In this series of blog posts, Eric Lippert describes a problem in object-oriented design using wizards and warriors as examples, where:
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 { }
and then adds a couple of rules:
- A warrior can only use a sword.
- A wizard can only use a staff.
He then goes on to demonstrate the problems you run into if you try to enforce these rules using the C# type system (e.g. making the Wizard
class responsible for making sure that a wizard can only use a staff). You violate the Liskov Substitution Principle, risk run-time exceptions or end up with code that is difficult to extend.
The solution he comes up with is that no validation is done by the Player class. It is only used to track state. Then, instead of giving a player a weapon by:
player.Weapon = new Sword();
state is modified by Command
s and according to Rule
s:
...we make a
Command
object calledWield
that takes two game state objects, aPlayer
and aWeapon
. When the user issues a command to the system “this wizard should wield that sword”, then that command is evaluated in the context of a set ofRule
s, which produces a sequence ofEffect
s. We have oneRule
that says that when a player attempts to wield a weapon, the effect is that the existing weapon, if there is one, is dropped and the new weapon becomes the player’s weapon. We have another rule that strengthens the first rule, that says that the first rule’s effects do not apply when a wizard tries to wield a sword.
I like this idea in principle, but have a concern about how it might be used in practice.
Nothing seems to prevent a developer from circumventing the Commands
and Rule
s by simply setting the Weapon
on a Player
. The Weapon
property needs to be accessible by the Wield
command, so it can't be made private set
.
So, what does prevent a developer from doing this? Do they just have to remember not to?