具有更严格约束的继承方法

本文关键字:继承 方法 约束 | 更新日期: 2023-09-27 17:49:14

假设我有以下内容:

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

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

Add(IComponent component)
Add(IComponent component, string name)
然而,假设我希望我的EntityContainer类只接受 EntityComponent对象,而不是任何实现IComponent的对象。

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

具有更严格约束的继承方法

'重写' Add方法,使其接收一个更具体的类型,这将不满足您的接口所暗示的契约。

你说容器接口有这些方法:

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

但是你想只允许EntityContainer实例(它实现了iccomponent),所以基本上你想这样:

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

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

正如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);
}

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

你的EntityContainer应该是这样的:

class EntityContainer : Container<EntityComponent> { 
   void Add(EntityComponent component) {
       // Actual add...
   }
   // Other methods..
}

参数中的协方差破坏了类型系统。想想看:

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++有这些),协变输出参数,和逆变in参数。由于我不知道的原因,这些都没有实现。实际上,在我看来,逆变参数有点傻,所以我很惊讶它们会出现。