代码之家  ›  专栏  ›  技术社区  ›  Hellium

在子类的重写函数中使用子接口

  •  0
  • Hellium  · 技术社区  · 8 年前

    你好,亲爱的面向对象专家,

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

    为了制作一个灵活的系统,我正在尝试使用 Interfaces Abstract 上课。涉及的实体非常简单:

    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.canbeuse(idamagable[])'标记为重写,但找不到可重写的适当方法

    自从 IDamagable 延伸 IAbilityTarget ,我不明白为什么会出错。我不想在 Fireball 为了避免错误:

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

    为了避免错误,而不必过载,我必须做哪些更改 CanBeUsed 对于每个子接口。

    2 回复  |  直到 8 年前
        1
  •  1
  •   Sweeper    8 年前

    因为IDamagable扩展了IAbilityTarget,所以我不明白为什么会出错。

    在这种情况下,如果 Use 方法在 Fireball 是否覆盖了 使用 方法 Ability .

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

    从编译器的角度来看,这应该是可行的: myAbility.Use 需要一个 IAbilityTarget[] ,你已经给了它但是,由于压倒一切, 我的能力。使用 调用中定义的方法 火球 ,它接受 IDamagable[] . 现在,你怎么能 MyDummyTarget 数组中的对象转换为 IDamagable 是吗?没有办法。

    您需要更改参数类型,以便两个方法的参数类型相同要么让他们都接受 可靠性目标[] 可识别[] . 我认为前者可能更有意义。

    从你的其他评论:

    当然,但是在fireball::use()函数中,我想缩短生命,但是这个属性只在IDmageable接口中定义!

    你可以这样做:

    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<IAbilityTarget> Ability<IDamagable> 是的。

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

        2
  •  3
  •   Richard    8 年前

    你是 Fireball 违反了liskov替代原则。

    因为一个客户提到 Ability 可以传递一个数组 IAbilityTarget 每一种方法。

    但是如果该引用的底层(运行时)类型是 火球 传递给方法的那些对象必须实现 IDamagable 这是一个更严格的要求你不能代替 火球 为了一个 能力 不改变方法的调用方式。

    通常重写必须是 较少的 在它们需要的前提条件中严格(不使用类型,而是使用传递对象的任何状态)你正在 更多 限制性的。