通过接口引用多个集合中的同一对象

dur*_*ill 11 c# interface

警告:此问题使用RPG的类比作为示例.

假设我正在使用C#制作这个我梦寐以求的角色扮演游戏.当玩家进入战斗时,会出现某种战场,其中包含与战斗相关的每个元素的参考,例如战场上可用的各种对手和物品.

现在所有这些元素只有一个但有几个角色:例如,Ally(通过直接继承的战斗机)能够在战场上移动,发出命令或被敌人作为目标.

现在,石头中的强大剑也在战斗中扮演了一些角色.显然它不能移动也不能发出命令,但它仍然可以成为目标,它可以(希望)被解除或使用.

所有这些行为都由我的代码中的接口表示,因此无论实现它的对象如何,都可以使用具有相同行为的所有对象.

代码示例:

public class Ally : Fighter, IMovable, IActor, ITarget
{
    // IMovable implementation
    public void Move(...) { ... }

    // IActor implementation
    public bool HasInitiative { get { ... } }

    public ICommand IssueCommand(...) { ... }

    // ITarget implementation
    public void OnTarget(...) { ... }

    // other code ...
}

public class MightySword : ITarget, ILiftable, IUsable
{
    // ITarget implementation
    public void OnTarget(...) { ... }

    // ... other interface implementation

    // other code ...
}
Run Code Online (Sandbox Code Playgroud)

现在我的问题如下:我的战地类如何保留对所有这些对象的引用?我提出了两种可能的解决方案,但目前还不清楚哪一种是最好的,以及是否找到更好的解决方案.

解决方案A:

引用几次,直接访问:使用此解决方案,每个对象在其实现的每个接口上引用一次,因此我的战场类如下所示:

public class Battlefield : ...
{
   IList<IMovable> movables;
   IList<ITarget> targets;
   IList<IActor> actors;
   // ... and so on
}
Run Code Online (Sandbox Code Playgroud)

而Battlefield.Update方法可能看起来像这样:

Update(...) 
{
  // Say every actor needs to target somebody else
  // it has nothing to do in the Update method,
  // but this is an example
  foreach (IActor actor in actors) 
  {
    if (actor.HasInitiative) 
    {
      ITarget target = targets[actor.TargetIndex];
      target.OnTarget(actor.IssueCommand(...));
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案允许我们直接访问对象的各种行为,但它引入了冗余,因为Ally必须被引用到动作,目标和actor中.这种冗余意味着更大的内存占用,并且会使对象添加到战场中变得更加复杂(每种类型现在应该从哪个集合添加/删除它).

此外,添加新界面意味着添加新集合以保存项目并管理相应的代码.

解决方案B:

引用一次,过滤器访问:使用此解决方案,所有对象都派生自单个类或接口(类似于IBattleElement),并存储在一个集合中.使用元素时,会根据其类型对其进行过滤:

Update(...)
{
    IList<IActor> actors = elements.OfType<Actor>();
    foreach (IActor actor in actors) 
    {
     ITarget target = elements.OfType<Target>()[actor.TargetIndex];
     target.OnTarget(actor.IssueCommand(...));
    }
}
Run Code Online (Sandbox Code Playgroud)

那里.没有更多的冗余,但我不喜欢使用"typeof"构造,我通常会尽量避免它.显然,这对于运行时性能来说更糟糕.

我在我的玩具项目中实现了解决方案A,但在这个项目中没有任何性能关键.答案不仅要考虑性能(内存和CPU方面),还要考虑为我或我的设计师可能必须执行的操作提供最佳设计的内容,例如为新行为添加新类的BattleElement或新接口,添加从战场中删除对象的实例,更常见的是在游戏逻辑中使用它们.

我为我的例子的一般愚蠢道歉,我希望我的英语足够好,我的问题至少有一点兴趣,并不表示设计本身的整体愚蠢.

Tim*_*ora 4

解决方案 A,但如果有理由的话,不要打折扣立即查询整个实例列表。

一般来说(大的概括,但有重要的先例),您将通过使用尽可能最少的派生类型(即仅您关心的接口)来实现最大的灵活性。您可能会通过提前处理和分组实例而不是事后将它们拆分来获得最佳性能。

性能提升是否重要是另一个问题。由于您可能正在谈论数十或数百个对象(而不是数百万个),因此性能不是我首先关心的问题。

从设计的角度来看,我会回避任何需要实时类型询问的事情(但这是一个意见问题)。

与性能一样,在围绕内存进行设计之前先分析内存。如果您使用 Visual Studio,那么您可以使用一些很棒的工具来执行此操作。想必您不会复制这些实例,因此无论如何开销都是最小的。

混合方法也可能有意义。

  • 从单个集合中添加/删除。
  • 公开特定类型的集合已过滤的只读集合。
  • 仅在需要时(重新)构建那些过滤后的集合。

public class Battlefield
{
    private readonly IList<IBattleElement> _all = new List<IBattleElement>();
    private IReadOnlyList<IActor> _actors;
    private bool _isDirty;

    /// <summary>
    ///     Gets a list of all elements.
    /// </summary>
    public IReadOnlyList<IBattleElement> All
    {
        get { return _all; }
    }

    /// <summary>
    ///     Gets a filtered list of elements that are <c>IActor</c>.
    /// </summary>
    public IReadOnlyList<IActor> Actors
    {
        get
        {
            if( _isDirty || _actors == null )
            {
                _actors = All.OfType<IActor>().ToList();
            }

            return _actors;
        }
    }

    /// <summary>
    ///     The one and only way to add new elements. This could be made thread-safe if needed.
    /// </summary>
    /// <param name="element"></param>
    public void AddElement( IBattleElement element )
    {
        _all.Add( element );
        _isDirty = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

...我的问题至少有一点点兴趣,并不表明设计本身总体上是愚蠢的。

一点也不傻。