【问题标题】:What do I need to change to implement SOLID design correctly?我需要更改哪些内容才能正确实施 SOLID 设计?
【发布时间】:2019-10-20 11:09:02
【问题描述】:

我正在尝试学习 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
    }

【问题讨论】:

  • 是什么让你认为你在这里破坏了 LSP?
  • @canton7 如果我以后从 IItem 添加一个新的派生接口,我将不得不更改播放器,据我了解这意味着它是不可替代的?
  • 这里看起来更像是打开/关闭违规(SOLID 的“O”),而不是 Liskov 替换问题。如果您需要进行更改,则意味着您的代码没有关闭进行修改。
  • @Corentin Pane,好点子!你能给我什么建议让我回到正轨吗?我还在努力学习:)
  • 如果你有一个派生自例如的类,你会破坏 LSP。 IArmor(我们称它为Shield),如果您使用IArmor 引用它,则该类的行为与使用Shield 引用它时的行为不同。如果有的话,这违反了打开/关闭原则,因为如果您添加另一种类型的设备,您需要修改您的 Player 类以正确处理它(以及使用 EquipmentType 的所有其他地方)。但是我不会在这里担心 - 如果您添加一种新型设备,您的 Player 很可能需要一些升级来应对

标签: c# .net-4.5


【解决方案1】:

Liskov 替换原则 通常与您如何定义类有关。如果您编写一个派生自其他类(或实现某个接口)的类,则想法是有人应该能够使用您的类,就好像它是该父类的实例一样。您的子类可能具有父类没有的其他行为(当然,将您的类当作父类的实例使用的人将无法访问它),但是所有行为父类在子类中应该保持原样。

在您的示例中,这可能意味着定义不适合 SlotMagicalWholeBodyArmor,因此如果您尝试访问其 Slot 属性,则会引发异常。将MagicalWholeBodyArmor 视为IArmor 的人在尝试查看它适合哪个插槽时会大吃一惊。

您编写的代码确实违反了 SOLID 规则,即 开放/封闭原则。 Open/Closed 原则的一个很好的经验法则是“如果我更改这段代码,我还必须更改其他地方的多少其他代码位?”。如果答案是“很多”,那可能是因为您违反了开放/封闭原则。

在您的情况下,添加一个新的 EquipmentType 枚举成员意味着您必须在 Player 类中找到该 switch 语句并添加一个新的情况。

如果只有一个 switch 语句,那还不错。如果您添加了一种新型设备,那么您的Player 类可能需要升级无论如何,因此修改 switch 语句作为其中的一部分是可以的。尝试以自己的方式解决这个问题将意味着大量的抽象,而收益却很少。

但是,如果您在许多不同的地方有很多很多 switch 语句,它们都在查看 EquipmentType,(并根据它做出不同的决定),那么您需要找到它们并全部修复它们,那么这就更严重地违反了开放/封闭原则,这可能表明您需要重新架构事物以将所有这些独立的、不同的逻辑位集中到一个地方。

【讨论】:

    猜你喜欢
    • 2011-08-04
    • 1970-01-01
    • 1970-01-01
    • 2016-12-05
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 2021-12-12
    • 2010-10-18
    相关资源
    最近更新 更多