我正在尝试学习SOLID设计,并认为自己犯了一个错误。我认为IItem
类中的Player
接口未遵循 Liskov替换原理,但是,我无法解决该问题。如果我从IItem添加新的界面图形,则必须更改Player的方法以添加用于处理它的外壳。
我希望Player类仅需要一种装备方法,因此需要帮助来了解我做错了什么以及如何正确进行。
我的界面的简化版本:
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; }
}
消耗型Player类:
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;
}
}
}
上下文枚举:
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
}
答案 0 :(得分:5)
Liskov替换原则通常是关于如何定义类的。如果您编写一个派生自其他某个类的类(或实现某个接口),则想法是某人应该能够像使用该父类的实例一样使用您的类。您的子类可能具有父类所没有的其他行为(当然,正在使用您的类的人,好像它是父类的实例,将无法访问它),但是所有的行为父类应保留在子类中。
在您的示例中,这可能意味着定义不适合MagicalWholeBodyArmor
的{{1}},因此,如果尝试访问其Slot
属性,则会引发异常。试图将Slot
视为MagicalWholeBodyArmor
的人,在试图查看它适合插入哪个插槽时会感到惊讶。
您编写的SOLID规则确实有点违反打开/关闭原则。打开/关闭原则的一个好的经验法则是:“如果我更改了这部分代码,那么我还必须在其他地方更改多少其他代码?”。如果答案是“很多”,那可能是因为您违反了开放/封闭原则。
对于您而言,添加一个新的IArmor
枚举成员意味着您必须去EquipmentType
类中查找该switch语句并添加一个新的案例。
如果只有一个switch语句,那还不错。如果添加了新类型的设备,则您的Player
类可能仍然需要升级 ,因此可以修改switch语句作为其中的一部分。尝试以这种方式设计您的方法将意味着大量的抽象,却几乎没有收获。
但是,如果您在许多不同的地方都有很多的switch语句,它们都着眼于Player
(并据此做出不同的决定),那么您需要全部找到并修复所有这些,那么就更大程度地违反了开放/封闭原则,这可能表明您需要重新架构事物,以将所有这些单独的,完全不同的逻辑放在一个地方。