【问题标题】:Using sub-interfaces in overriden functions of sub-classes在子类的覆盖函数中使用子接口
【发布时间】:2018-07-19 08:27:30
【问题描述】:

尊敬的 OOP 专家您好,

如果之前有人问过这个问题,我很抱歉,我还没有找到任何类似的问题(我可能没有合适的词来解释)。

为了制作一个灵活的系统,我尝试使用InterfacesAbstract 类。涉及的实体非常简单:

public interface IAbilityTarget
{
    // A bunch of properties and functions
}

public interface IDamagable : IAbilityTarget
{
    int Life { get; set; }
}

public abstract class Ability
{
    public abstract bool CanBeUsed( IAbilityTarget[] targets );
    public abstract void Use( IAbilityTarget[] targets );
}    

public class Fireball : Ability
{
    public override bool CanBeUsed( IDamagable[] targets )
    {
        return true ; // for the sake of the example
    }

    public override void Use( IDamagable[] targets )
    {
        for( int index = 0 ; index < targets.Length ; ++index )
            targets[index].Life -= 1 ;
    }
}

我的问题如下:为什么我有CS0115 错误?

错误 CS0115:`Fireball.CanBeUsed(IDamagable[])' 被标记为覆盖,但找不到合适的覆盖方法

由于IDamagable 扩展了IAbilityTarget,我不明白为什么会出现错误。我不想在Fireball 中实现以下内容以避免错误:

public override bool CanBeUsed( IAbilityTarget[] targets )
{
    return true;
}
public bool CanBeUsed( IDamagable[] targets )
{
    return CanBeUsed( (IAbilityTarget[]) targets );
}

为了避免错误,我必须进行哪些更改,而不必为每个子接口重载CanBeUsed

【问题讨论】:

  • IDamagable 比 IAbilityTarget 更专业。在抽象类中指定 IAbilityTarget 意味着实现者只能确定 IAbilityTarget 的成员是否存在。

标签: c# oop inheritance interface


【解决方案1】:

你是 Fireball 违反了里氏替换原则。

因为引用了Ability 的客户端可以将IAbilityTarget 的数组传递给它的每个方法。

但如果该引用的底层(运行时)类型确实是Fireball,则传递给方法的那些对象必须实现IDamagable,这是一个更严格的要求。 IE。如果不改变调用方法的方式,就不能用 Fireball 替换 Ability

通常,覆盖必须less严格遵守它们需要的前置条件(不使用类型,而是使用传递对象的任何状态)。你的限制越来越了。

【讨论】:

  • 感谢您的回答。这是我怀疑的,但我也尝试了以下Ability::CanBeUsed( IDamagable[] targets ) /* */ Fireball::CanBeUsed( IAbilityTarget[] targets ),结果是一样的,这是合乎逻辑的,因为Fireball::CanBeUsed可能需要一个只能在IDamageable接口中访问的函数。
  • @Hellium 在 C# 等语言的有限静态类型系统中,可以实现的目标是有限的。您要么必须重构类型以便不需要协方差,要么最终进行运行时检查。
【解决方案2】:

由于 IDamagable 扩展了 IAbilityTarget,我不明白为什么会出现错误。

如果Fireball 中的Use 方法确实覆盖了Ability 中的Use 方法,则会出现不一致的情况。

class MyDummyTarget : IAbilityTarget { ... }

...

Ability myAbility = new Fireball();
myAbility.Use(new IAbilityTarget[] { new MyDummyTarget() });

从编译器的角度来看,这应该可以工作:myAbility.Use 需要一个 IAbilityTarget[],这是你给它的。但是,由于覆盖,myAbility.Use 调用了在Fireball 中定义的方法,该方法接受IDamagable[]。现在,如何将数组中的MyDummyTarget 对象转换为IDamagable?没办法。

您需要更改参数类型,以使两种方法的参数类型相同。要么让他们都接受IAbilityTarget[]IDamagable[]。我认为前者可能更有意义。

来自您的其他评论:

当然,但是在 Fireball::Use() 函数中,我想减少生命,但是这个属性只在 IDamageable 接口中定义!

你可以这样做:

public override void Use( IAbilityTarget[] targets )
{
    for( int index = 0 ; index < targets.Length ; ++index ) {
        if (targets[index] is IDamagable) {
            ((IDamagable)targets[index]).Life -= 1 ;
        }
    }
}

如果您想要更安全的方法,

public abstract class Ability<T> where T : IAbilityTarget
{
    public abstract bool CanBeUsed( T[] targets );
    public abstract void Use( T[] targets );
}    

public class Fireball : Ability<IDamagable>
{
    public override bool CanBeUsed( IDamagable[] targets )
    {
        return true ; // for the sake of the example
    }

    public override void Use( IDamagable[] targets )
    {
        for( int index = 0 ; index < targets.Length ; ++index )
            targets[index].Life -= 1 ;
    }
}

但这会阻止您将Ability&lt;IAbilityTarget&gt; 转换为Ability&lt;IDamagable&gt;

有时你只需要接受类型系统的限制。

【讨论】:

  • 既然myAbility 已经定义为Ability,为什么myAbility.Use 会调用Fireball 中定义的方法? rextester.com/JZTDJ35159
  • @Hellium Ability 中的Use 方法是否有主体?不。当一个抽象方法被调用时,你期望发生什么?记住在运行时mAbilityFireball 的对象。
  • Does the Use method in Ability have a body? No,但是如果我想在这个函数中发生一些事情怎么办?
  • @Hellium 我刚刚看了你的链接。您应该注意您的链接与问题中提供的代码有何不同。在您的链接中,您没有使用 override 关键字,因此不会覆盖方法。但在这里,你试图让Fireball.Use 覆盖Ability.Use
  • @Hellium 我在编辑中提供的实现解决了你的问题吗?
猜你喜欢
  • 1970-01-01
  • 2016-12-09
  • 1970-01-01
  • 2020-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-30
相关资源
最近更新 更多