【问题标题】:How to combine objects into one list, when they are inherited from one interface?当对象从一个接口继承时,如何将它们组合成一个列表?
【发布时间】:2025-12-16 04:35:01
【问题描述】:

我正在学习 C# 并尝试做简单的面向对象练习。

我有三个武器类,“弓、匕首、矛”,所以我做了一个接口,并从这个接口继承了IWeapon。

现在用户必须选择一种武器,所以我想制作武器收藏,我正在尝试制作 IWeapons 列表,这是正确的方法吗?制作列表类型的 IWeapon 不是不好的做法吗?因为,我知道接口就像一个合同,我认为用接口类型制作 List 是个坏主意。一种方法是将接口更改为抽象类,但我想使用接口。

 private static List<IWeapon> weapons = new List<IWeapon>();

方法对不对?

【问题讨论】:

  • 是的,这没有问题,只要所有三个武器类别都没有自己的属性......
  • 是的,我相信拥有一个接口列表并没有错。你的设计没问题
  • 这其实是一个很常见的模式,这里没有错。
  • 虽然你的方法很好,但这些问题都是基于意见的,因此在 * 上很糟糕。
  • 您可能对这个练习别无选择,但可能想阅读 Eric Lippert 题为 Wizards and Warriors 的博客系列(链接到第 1 部分,共 5 个部分)

标签: c# .net oop


【解决方案1】:

您的解决方案的唯一问题是静态成员应该是线程安全的。这就像 C# 开发人员之间的约定。所以要么把它改成非静态的,最好是只读的:

private readonly List<IWeapon> weapons = new List<IWeapon>();

或者使用线程安全的集合:

private static ConcurrentBag<IWeapon> weapons = new ConcurrentBag<IWeapon>();

【讨论】:

  • 我从未听说过这样的约定。最佳实践是除非需要,否则不要为线程安全编程,因为这会增加复杂性。
  • @Sefe MS 保证所有静态 .NET API 都是线程安全的。如果您研究其他流行的库,它总是带有静态成员的线程安全的。最佳做法是根本不发布代码(您将来需要支持它)
  • “静态成员应该是线程安全的” - 仅当多线程是一个问题时,但对于给定的问题,很难判断给出这样的建议。
  • @Sinatr 如果没有多线程 - 那为什么是静态的?这就是为什么我展示了两个选项
  • “如果不需要,代码荒谬” - 导致 KISSYAGNI ;) 简而言之,您的答案是正确且有用的,但前提是多线程是问题。
【解决方案2】:

对此我有两个想法:

  1. 我可以很容易地想象一个人想要使用特定界面的集合的情况。例如,如果您正在编写下载队列。您将拥有武器和盾牌以及玩家和村庄,它们都实现了 IDownloadableThing 接口。您的队列代码将有一个 IDownloadableThings 集合,因为它不关心它实际下载的内容,它只关心那个东西知道它的 URL 或文件路径或其他什么。

  2. 我对开发者法令有点警惕。任何语言都应该注意一些好的想法和陷阱,但是上下文可以改变一切。当你试图弄清楚“武器”是否应该是一个抽象类时,问自己几件事。实现武器的类也应该被允许成为盾牌吗?如果锁定多重继承很重要,那么抽象类就是这样做的方法。实现“武器”的类会想要分享很多或任何常见的行为吗?例如,是否有一个CalculateDamage() 方法在所有“武器”实现之间基本相同。一旦你知道了接口类的目的是什么,那么选择违反开发指南会更容易,因为情况需要它或重新考虑你的方法。

【讨论】:

    【解决方案3】:

    使用接口作为列表的值并没有错。

    但是,这可能不适合您的具体情况。您说您希望玩家从武器列表中进行选择。现在考虑以下场景:

    1. 你的游戏中有一个玩家,他选择了一种武器。你创建了一个完整的武器实例列表,没有人需要或使用。这只会浪费内存。
    2. 游戏中有多个玩家,他们都选择相同的武器。 这可能会更糟。如果您像这样分配武器:player.Weapon = weapons[1],那么两个玩家将拥有相同的武器instance。如果一个用户为他的武器分配了增益,或者如果武器退化或损坏,两个玩家的武器都会受到影响,因为它实际上是同一种武器。这样想,你和我都想要一个巧克力蛋糕。店家可以给我们两个一样的蛋糕,在这种情况下,如果你吃了它,我就不吃了。或者他们可以为我们俩烤新蛋糕,这样我们每个人都可以拥有自己的蛋糕。

    在这种情况下,适当的解决方案是保存武器类型并将其呈现给玩家。然后根据玩家的选择创建一个新的武器实例。

    有几种不同的方法可以做到这一点,其中最简单的方法是创建一个包含武器类型的枚举并将它们呈现给玩家。

    因此,您可以使用:

    public enum WeaponType
    {
        Bow,
        Dagger,
        Spear
    }
    

    现在你需要创建玩家选择的武器实例:

    public IWeapon CreateWeapon(WeaponType weaponType)
    {
        switch(weaponType)
        {
            case WeaponType.Bow:
            // Create Bow...
            case WeaponType.Dagger:
            // Create Dagger...
            case WeaponType.Spear:
            // Create Spear...
        }
    }
    

    这是工厂设计模式的基础。我强烈建议你看看它。这是一个很好的起点:http://www.dofactory.com/net/factory-method-design-pattern

    【讨论】:

    • 非常感谢,您的回答对我帮助很大。