【问题标题】:Pattern for a list that can contain two types of items可以包含两种类型项目的列表的模式
【发布时间】:2018-10-30 17:47:20
【问题描述】:

我正在尝试找出一种很好的方法来采用我们最近经历的 API 更改,该更改“弱化”了我们一个对象的类型。

用户过去可以提供对“狗”的引用列表。我们已决定现在接受“Dogs”和“Cats”。

客户端库使用强类型引用,因此该属性当前如下所示:

public List<IReference<Dog>> occupants { get; set; }

我考虑将其更改为:

public List<IReference<Animal>> occupants { get; set; }

但我不喜欢以前的编译时验证现在是运行时(和服务器端)验证。我宁愿有一些更强大的类型,比如:

public List<Occupant> occupants { get; set; }

public class Occupant : IReference<Animal> {
    IReference<Animal> _internalReference;
    public Occupant(IReference<Dog> dog) { _internalReference = dog }
    public Occupant(IReference<Cat> cat) { _internalReference = cat }
    //... (Implement IReference<Animal> as a pass-through to _internalReference)
}

不幸的是,由于 C# 不允许隐式运算符从接口转换,现有用户更新其客户端库将不得不经历数百行代码更改才能将所有现有 Dog 引用“包装”在 Occupant 中构造函数。

所以接下来我考虑使用一些辅助方法为列表创建一个新类:

public class OccupantList : List<Occupant> { 
    public void Add(IReference<Dog> newDog) => base.Add(new Occupant(newDog));
    public void Add(IReference<Cat> newCat) => base.Add(new Occupant(newCat));
    // For backwards compatibility with new list assignments
    public static implicit operator OccupantList(List<IReference<Dog>> legacy) =>
        new OccupantList(legacy.Select(dog => new Occupant(dog)));
}

不幸的是,C# 没有办法在幕后覆盖核心 ICollection.Add 实现,各种反射实用程序和我的 JSON 序列化程序都使用它来填充这个列表,所以当它给出要添加的对象时它会抱怨还不是Occupant 类型。我个人遇到了我使用的 JSON 反序列化器的问题。我可以为这个类编写一个自定义的反序列化器,但我认为这表明试图继承 List&lt;Occupant&gt; 并添加对不继承自 Occupant 的类型的支持是一个注定要失败的坏主意。

有没有人有任何想法或例子可以引导我们朝着正确的方向前进?


这只是“我的问题”

真正令人震惊的是,这甚至不是 API 层的向后不兼容。 API 使用动态类型(带有 Python 解释器的 JSON),因此唯一的变化是以下 JSON:

{ "occupants": [{"ref_id":"abc123"}, {"ref_id":"zzz999"}] }

变成:

{ "occupants": [{"ref_id":"abc123", "ref_type": "Dog"}, {"ref_id":"gdr736", "ref_type":"Cat"}] }

如果未指定类型,则使用向后兼容规则将引用视为“狗”。

不幸的是,因为我很早就决定为这些引用对象添加编译时类型,所以 C# 客户端库用户是唯一遭受类型弱化的用户,而 API 仍然能够动态找出合适的类型在运行时。

【问题讨论】:

  • 另一个(重大)更改是将set 访问器更改为private(对于occupants),然后提供Add(Dog dog)Add(Cat cat) 方法(而不仅仅是occupants.Add )。还没有真正考虑过,只是一个头脑风暴的想法......

标签: c# generics design-patterns covariance backwards-compatibility


【解决方案1】:

最坏的情况:

您会讨厌这个,但如果该 API 有任何消费者,最佳实践表明您的重大更改意味着现在是时候对您的 API 进行版本控制了。

充其量:

我没有看到您提到的 API 控制器的签名,但如果有问题的模型被定义为该方法的参数,您可能可以通过重载控制器方法而侥幸逃脱,让模型绑定器处理不同的形状,然后在某种中间件或自动映射器中解决问题。

为什么不通过改变模型来解决?

因为听起来您已经为某种外部消费发布了这个 API,所以您真的真的不应该违反该输入模型所描述的合同。

【讨论】:

  • 这是一个有趣的观点。 API 本身不是向后不兼容的,因为它是动态类型的(使用 JSON)。我已经用这方面的一些细节更新了我的请求。基本上,这只是“我的问题”,直接进行 JSON 通信的 API 用户不会注意到这种变化。
猜你喜欢
  • 1970-01-01
  • 2011-02-03
  • 1970-01-01
  • 1970-01-01
  • 2020-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多