3

假设我有以下内容:

class EntityContainer : Container { }
class EntityComponent : Component { }

Container 有两种向容器添加新组件的方法,它们是:

Add(IComponent component)
Add(IComponent component, string name)

但是,假设我希望我的EntityContainer EntityComponent接受对象,而不是任何实现IComponent.

起初,我以为我可以简单地隐藏或覆盖基类的Add()方法,但似乎签名必须完全匹配。那么,最好的方法是什么?

4

2 回答 2

4

“覆盖” Add 方法以使其接收更具体的类型不会满足您的接口所暗示的合同。

你说Container接口有这些方法:

void Add(IComponent component);
void Add(IComponent component, string name);

但是你只想允许 EntityContainer 实例(实现 IComponent),所以基本上你想要这个:

void Add(EntityComponent component);
void Add(EntityComponent component, string name);

你不能像这样实现(甚至在语义上)Container 接口,因为在你的接口中你说你可以添加任何实现 IComponent 的元素。你正在改变原来的合同!

正如 Morten 在评论中指出的那样,您可以执行以下操作:

class EntityContainer : Container { 
   void Add(IComponent component) {
       var entityComponent = component as EntityComponent;
       if(entityComponent == null)
          throw new InvalidOperationException("Can only add EntityComponent instances");
       // Actual add...
   }
   // Other methods..
}

但我建议你不要这样做。违反接口暗示的契约应该是例外,而不是规则。此外,如果你这样做,你无法知道容器真正期望什么,直到运行时。这不是一种直观的行为,它很可能会导致微妙的问题。如果您只想接受特定类型的组件,则可以使用泛型。这样,你不仅可以应用你想要的约束,还可以获得强类型,并且你的意图会更清晰。它看起来像这样:

interface Container<T> where T : IComponent {
   void Add(T component);
   void Add(T component, string name);
}

这意味着您的容器将保存指定类型的元素,但它应该实现(或扩展,如果它是一个类)接口 IComponent。所以你不能创建一个Container<Object>,因为它没有实现 IComponent。

您的 EntityContainer 将如下所示:

class EntityContainer : Container<EntityComponent> { 
   void Add(EntityComponent component) {
       // Actual add...
   }
   // Other methods..
}
于 2012-12-23T06:33:48.243 回答
3

in参数的协方差破坏了类型系统。考虑一下:

void f(Container c) { c.Add(new NonEntityComponent); }
⋮
var ec = new EntityContainer();
f(ec);

类型系统中没有任何东西可以阻止这种情况。派生方法 inEntityContainer声明为的Add(EntityComponent ec)事实无济于事,因为f()从未听说过EntityContainer.

允许协变参数会产生破坏类型系统的情况,例如,EntityContainer.Add()传递 aNonEntityComponent并将其视为EntityComponent.

可以连贯地实现某些形式的方差:协变返回类型(C++ 有这些)、协变输出参数和逆变输入参数。由于我不知道的原因,这些没有实施。实际上,逆变参数会有点傻,IMO,所以看到它们出现我会很惊讶。

于 2012-12-23T06:03:45.820 回答