假设我有以下内容:
class EntityContainer : Container { }
class EntityComponent : Component { }
Container有两种方法可以向容器添加新组件,即:
Add(IComponent component)
Add(IComponent component, string name)
但是,假设我希望我的EntityContainer
课程仅使用 EntityComponent
个对象,而不是任何实现IComponent
的对象。
起初,我认为我可以简单地隐藏或覆盖基类的Add()
方法,但似乎签名必须与完全匹配。那么,最好的方法是什么?
答案 0 :(得分: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的元素。你正在改变原来的合同!
正如莫滕在评论中指出的那样,你可以这样做:
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..
}
但我建议你不要这样做。打破界面所暗示的合同应该是例外,而不是规则。此外,如果您这样做,在运行时之前您无法知道Container真正期望的内容。这不是一种直观的行为,它很可能会导致细微的问题。如果您只想接受特定类型的组件,则可以使用泛型。通过这种方式,您不仅可以应用您想要的约束,还可以获得强力打字,并且您的意图将更加清晰。它看起来像这样:
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..
}
答案 1 :(得分:3)
参数中的协方差会破坏类型系统。考虑一下:
void f(Container c) { c.Add(new NonEntityComponent); }
⋮
var ec = new EntityContainer();
f(ec);
类型系统中的任何内容都无法阻止此操作。 EntityContainer
中派生的方法声明为Add(EntityComponent ec)
这一事实无济于事,因为f()
从未听说过EntityContainer
。
允许协变参数会产生破坏类型系统的情况,例如,EntityContainer.Add()
传递NonEntityComponent
并将其视为EntityComponent
。
某些形式的方差可以连贯地实现:协变返回类型(C ++具有这些),协变输出参数和参数中的逆变。由于我不知情的原因,这些都没有实施。实际上,逆变参数会有点愚蠢,IMO,所以我会惊讶地看到它们出现。