当我需要存储多个不相关的类型但按需提供特定类型的情况时,我应该如何处理?
题
我正在开发一个文件编辑器,该文件由我们使用的一个重要的内部测试工具使用。该工具本身庞大、复杂,重构或重写将占用更多的资源,超出了我们在可预见的未来所能投入的资源,所以当涉及到大规模修改时,我的双手被束缚。我必须使用 .NET 语言。
这些文件是该工具使用的四个类的 XML 序列化版本(我们将它们称为 A、B、C 和 D)。当一切顺利时,类形成树结构。我们的编辑器的工作原理是加载一组文件,反序列化它们,找出它们之间的关系,并跟踪它可以找到的任何不良状态。我们的想法是放弃手动编辑这些文件,因为这会带来大量错误。
对于特定类型的错误,我想维护有问题的所有文件的集合。所有四个类都可能有问题,我想尽可能减少代码的重复。一个重要的要求是用户需要能够获得成套的物品;例如,他们需要获取所有有错误的 A 对象,并告诉他们迭代整个集合并挑选出他们想要的东西,与 GetAs()
方法。因此,我的第一个想法是制作一个与反序列化对象相关的通用项目和一些元数据来指示错误:
public class ErrorItem<T>
{
public T Item { get; set; }
public Metadata Metadata { get; set; }
}
然后,我有一个可以保存所有错误项目的集合类,并使用辅助方法在用户需要时提取特定类的项目。这就是麻烦开始的地方。
没有一个类继承自共同的祖先(除了 Object
)。这可能是最初设计的一个错误,但我花了几天时间思考它,除了唯一标识每个项目的 GUID 属性之外,这些类确实没有太多共同点,所以我可以明白为什么原始设计者没有通过继承将它们联系起来。这意味着统一的错误集合需要存储 ErrorItem<Object>
对象,因为我没有基类或接口来限制传入的内容。然而,这使得这个统一集合的想法对我来说有点粗略:
Public Class ErrorCollection
{
public ErrorItem<Object> AllItems { get; set; }
}
然而,这会对公共接口产生影响。我真正想要的是返回适当的 ErrorItem
像这样的泛型类型:
public ErrorItem<A>[] GetA()
这是不可能的,因为我只能存储 ErrorItem<Object>
!我在脑子里思考了一些解决方法;主要包括创建一个新的 ErrorItem
即时的适当类型,但感觉有点丑陋。另一个想法是使用 Dictionary
按类型组织项目,但这似乎仍然不对。
有某种模式可以帮助我吗?我知道解决此问题的最简单方法是添加 A、B、C 和 D 派生自的基类,但我试图对原始工具产生尽可能小的影响。任何解决方法的成本是否足够大,以至于我应该推动更改初始工具?
解决方案
如果 A、B、C 和 D 没有任何共同点,那么添加基类不会真正给你带来任何好处。它只是一个空类,实际上与对象相同。
我只是创建一个没有泛型的 ErrorItem 类,将 Item 设为一个对象,并在您想要使用引用的对象时进行一些转换。如果您想使用 Guid 之外的 A、B、C 或 D 类的任何属性或方法,您无论如何都必须强制转换它们。
其他提示
这是你想要的?
private List<ErrorItem<object>> _allObjects = new List<ErrorItem<object>>();
public IEnumerable<ErrorItem<A>> ItemsOfA
{
get
{
foreach (ErrorItem<object> obj in _allObjects)
{
if (obj.Item is A)
yield return new ErrorItem<A>((A)obj.Item, obj.MetaData);
}
}
}
如果你想缓存 ItemsOfA
你可以轻松做到这一点:
private List<ErrorItem<A>> _itemsOfA = null;
public IEnumerable<ErrorItem<A>> ItemsOfACached
{
if (_itemsOfA == null)
_itemsOfA = new List<ErrorItem<A>>(ItemsOfA);
return _itemsOfA;
}
到目前为止,我给出的答案是 Friedguybob 和 Mendelt Siebenga 的答案的组合。
正如 Mendelt Siebenga 指出的那样,添加基类只会污染命名空间并引入类似的问题。我可以更好地控制哪些项目可以进入集合,但我仍然需要存储 ErrorItem<BaseClass>
并且仍然进行一些铸造,所以我会遇到具有相同根本原因的稍微不同的问题。这就是我选择这篇文章作为答案的原因:它指出无论如何我都必须进行一些强制转换,并且 KISS 会指示额外的基类和泛型太多了。
我喜欢 Friedguybob 的答案,不是因为解决方案本身,而是因为它提醒我 yield return
, ,这将使非缓存版本更容易编写(我打算使用 LINQ)。我认为缓存版本更明智,尽管预期的性能参数不会使非缓存版本明显变慢。