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

本文关键字:对象 集合 接口 引用 | 更新日期: 2023-09-27 18:32:49

警告:这个问题以RPG为例。

假设我正在使用 C# 制作这个我一直梦寐以求的 RPG。当玩家进入战斗时,会出现某种战场,其中包含与战斗相关的每个元素的引用,例如战场上可用的各种对手和物品。

现在,所有这些元素都有一个但几个角色:例如,盟友(通过直接继承成为战士(能够在战场上移动,发出命令或成为敌人的目标。

现在,石中剑在战斗中也有几个角色。显然,它不能移动或发出命令,但它仍然可以成为目标,并且(希望(可以解除或使用。

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

代码示例:

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 ...
}

现在我的问题如下:我的《战地风云》类应该如何保存对所有这些对象的引用?我想出了两种可能的解决方案,但目前尚不清楚哪一个是最好的,以及是否可以找到更好的解决方案。

解决方案 A:

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

public class Battlefield : ...
{
   IList<IMovable> movables;
   IList<ITarget> targets;
   IList<IActor> actors;
   // ... and so on
}

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(...));
    }
  }
}

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

此外,添加新接口意味着添加新集合来保存项目并管理相应的代码。

解决方案 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(...));
    }
}

那里。没有更多的冗余,但我不喜欢使用"typeof"结构,我通常会尽量避免它。显然,这在运行时性能方面更糟。

我在玩具项目中实施了解决方案 A,但此项目中没有任何性能关键项。答案不仅应该考虑性能(内存和 CPU 方面(,还应该考虑什么为我或我的设计师可能必须执行的操作提供最佳设计,例如添加新的 BattleElement 类或用于新行为的新接口,在《战地风云》中添加和删除对象实例,以及在游戏逻辑中更普遍地使用它们。

对于我的例子的愚蠢,我深表

歉意,我希望我的英语足够好,并且我的问题至少带有一点兴趣,并且并不表明设计本身的整体愚蠢。

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

解决方案 A,但如果有理由这样做,请不要忽略一次查询整个实例列表。

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

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

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

与性能一样,在围绕它进行设计之前,先配置文件内存。如果你正在使用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;
    }
}

。并且我的问题至少带有一点兴趣和 并不表示设计本身的整体愚蠢。

一点也不傻。

我会选择解决方案 A;额外的内存占用实际上是有多个指向对象同一实例的指针,并且代码更容易维护以实现未来的可扩展性。

顺便说一句...像这样的问题更多是基于意见的,所以我认为没有正确或错误的答案。例如,我可以调整解决方案 A 来实现 IDictionary 而不是 IList,您无需担心索引维护!

我会创建最后一个接口。 IBattleFieldObject,因为它们都是通用的。 然后通过依赖注入的构造函数将它们全部放在列表中。 然后在您的 Update 方法中,我将使用 lambda 通过 linq 提取所有 IActor。 然后执行与IActor相关的操作。 此外,在更新中,您可以处理模拟战场所需的任何其他接口。

var IActors = (from list in battlefieldobjects where list is IActor select list).ToList();

或者你可以做一个扩展:

public static List<IActor> GetActors(this List<IBattleFieldObject> list)
{
    return list.Where(t => t is IActor).ToList();
}

用法:战场物体。GetActor((;

它将消除删除各种集合中的对象的困难。当需要删除对象时。只需将其从列表集合中删除即可。

此外,如果您的游戏添加了任何新功能或行为......您将能够将IBattleFieldObject接口添加到新的"事物"上,然后在更新方法中加入处理该新"事物"的代码。