问题
I'm trying to learn SOLID design and think I have made a mistake. I think that the IItem
interface does not follow Liskov substitution principle within my Player
class however, I can't work out how to fix this. If I add a new interface drawing from IItem I would have to change Player's method to add a case to handle it.
I would like for the Player class to only need one method for equip so need help understanding what I have done wrong and how to do it correctly.
Simplified version of my interfaces:
interface IItem
{
string Name { get; set; }
int Value { get; set; }
Quality Quality { get; set; }
EquipmentType Type { get; set; }
}
interface IWeapon : IItem
{
}
interface IArmour : IItem
{
int Defence { get; set; }
Slot Slot { get; set; }
}
The consuming Player class:
class Player
{
private Dictionary<Slot, IArmour> armour = new Dictionary<Slot, IArmour>();
private IWeapon weapon;
public bool Equip(IItem item)
{
switch (item.Type)
{
case EquipmentType.Armour:
var armour = item as IArmour;
if (this.armour.ContainsKey(armour.Slot))
{
return false;
}
this.armour.Add(armour.Slot, armour);
return true;
case EquipmentType.Weapon:
var weapon = item as IWeapon;
throw new NotImplementedException();
default:
return false;
}
}
}
Enums for context:
enum Slot
{
Head = 0,
Soulders = 1,
Gloves = 2,
Neck = 3,
RRing = 4,
LRing = 5,
Torso = 6,
Legs = 7,
Boots = 8,
Bracers = 9,
Belt = 10,
}
enum EquipmentType
{
Armour = 0,
Weapon = 1
}
回答1:
The Liskov Substitution Principle is typically about how you define your classes. If you write a class which derives from some other class (or implements some interface), the idea is that someone should be able to use your class as if it was an instance of that parent class. Your subclass may have additional behaviour which the parent class doesn't have (and someone who's using your class as if it was an instance of the parent class won't be able to access this, of course), but all of the behaviour that the parent class has should stay intact in the child class.
In your example, that might mean defining MagicalWholeBodyArmor
which doesn't fit into a Slot
, and so throws an exception if you try and access its Slot
property. Someone who's treating the MagicalWholeBodyArmor
as if it was an IArmor
would be in for a surprise when they tried to see what slot it fits into.
The SOLID rule that you code does violate a little bit is the Open/Closed principle. A good rule of thumb for the Open/Closed principle is "If I change this bit of code, how many other bits of code in other places do I also have to change?". If the answer is "lots", then that's probably because you're violating the Open/Closed principle.
In your case, adding a new EquipmentType
enum member means that you'll have to go and find that switch statement in your Player
class and add a new case.
If there's just the one switch statement, then that isn't too bad. If you add a new type of equipment then your Player
class probably needs an upgrade anyway, so modifying the switch statement as part of that is fine. Trying to architect your way around this would mean a significant amount of abstraction for very little gain.
However, if you have lots and lots of switch statements in lots of different places which all look at EquipmentType
, (and make different decisions based on it), and you'd need to find them all and fix them all, then that's a bigger violation of the Open/Closed principle, and it's probably an indication that you need to re-architect things to bring all of those separate, disparate bits of logic into a single place.
来源:https://stackoverflow.com/questions/58472382/what-do-i-need-to-change-to-implement-solid-design-correctly