是否可以使用派生参数而不是基础参数覆盖方法?

时间:2013-07-30 10:18:41

标签: c# inheritance override abstract-class

我陷入了这种情况:

  1. 我有一个名为 Ammo 的抽象类,其中AmmoBoxClip为子项。
  2. 我有一个名为Weapon的抽象类,其中FirearmMelee为子项。
  3. Firearm是抽象的,ClipWeaponShellWeapon为孩子。
  4. Firearm内,有一个void Reload(Ammo ammo);
  5. 问题是,ClipWeapon可以使用ClipAmmoBox来重新加载:

    public override void Reload(Ammo ammo)
    {
        if (ammo is Clip)
        {
            SwapClips(ammo as Clip);
        }
        else if (ammo is AmmoBox)
        {
            var ammoBox = ammo as AmmoBox;
            // AddBullets returns how many bullets has left from its parameter
            ammoBox.Set(clip.AddBullets(ammoBox.nBullets));
        }
    }
    

    ShellWeapon只能使用AmmoBox重新加载。我能做到这一点:

    public override void Reload(Ammo ammo)
    {
        if (ammo is AmmoBox)
        {
            // reload...
        }
    }
    

    但这很糟糕,因为即使我正在检查以确保它是AmmoBox类型,从外部看来,ShellWeapon似乎也可以Clip ,因为Clip也是Ammo

    或者,我可以从Reload移除Firearm,并将ClipWeaponShellWeapon与我需要的特定参数一起删除,但这样做会失去优势多态性,这不是我想要的。

    如果我可以覆盖Reload内的ShellWeapon,那么这不是最佳选择:

    public override void Reload(AmmoBox ammoBox)
    {
       // reload ... 
    }
    

    当然我试过了,它没有用,我收到一个错误,说签名必须匹配或者其他什么,但这不应该是'逻辑'有效吗?由于AmmoBoxAmmo

    我该如何解决这个问题?一般来说,我的设计是否正确? (注意我使用的是接口IClipWeaponIShellWeapon,但是我遇到了麻烦,所以我转而使用了类)

    提前致谢。

4 个答案:

答案 0 :(得分:14)

  

但这不应该是'逻辑'有效吗?

没有。您的界面显示调用者可以传入任何 Ammo - 您要限制它需要AmmoBox,这更具体。

如果有人写信,你会发生什么:

Firearm firearm = new ShellWeapon();
firearm.Reload(new Ammo());

?那应该是完全有效的代码 - 所以你想让它在执行时爆炸吗?静态类型的一半是避免这种问题。

可以使用弹药类型Firearm泛型:

public abstract class Firearm<TAmmo> : Weapon where TAmmo : Ammo
{
    public abstract void Reload(TAmmo ammo);
}

然后:

public class ShellWeapon : Firearm<AmmoBox>

这可能是也可能不是一种有用的做事方式,但至少值得考虑。

答案 1 :(得分:3)

您摔跤的问题来自于需要根据弹药的运行时类型调用不同的实现。从本质上讲,重新加载的动作需要相对于两个而不是一个对象是“虚拟的”。此问题称为double dispatch

解决这个问题的一种方法是创建一个类似访客的结构:

abstract class Ammo {
    public virtual void AddToShellWeapon(ShellWeapon weapon) {
        throw new ApplicationException("Ammo cannot be added to shell weapon.");
    }
    public virtual void AddToClipWeapon(ClipWeapon weapon) {
        throw new ApplicationException("Ammo cannot be added to clip weapon.");
    }
}
class AmmoBox : Ammo {
    public override void AddToShellWeapon(ShellWeapon weapon) {
        ...
    }
    public override void AddToClipWeapon(ClipWeapon weapon) {
        ...
    }
}
class Clip : Ammo {
    public override void AddToClipWeapon(ClipWeapon weapon) {
        ...
    }
}
abstract class Weapon {
    public abstract void Reload(Ammo ammo);
}
class ShellWeapon : Weapon {
    public void Reload(Ammo ammo) {
        ammo.AddToShellWeapon(this);
    }
}
class ClipWeapon : Weapon {
    public void Reload(Ammo ammo) {
        ammo.AddToClipWeapon(this);
    }
}

“魔法”发生在武器子类的Reload的实现中:他们让弹药本身进行双重调度的“第二站”,而不是决定他们获得什么样的弹药。方法是合适的,因为他们的AddTo...Weapon方法知道他们自己的类型,以及他们被重新加载的武器的类型。

答案 2 :(得分:3)

您可以使用带有接口扩展的合成而不是多重继承:

class Ammo {}
class Clip : Ammo {}
class AmmoBox : Ammo {}

class Firearm {}
interface IClipReloadable {}
interface IAmmoBoxReloadable {}

class ClipWeapon : Firearm, IClipReloadable, IAmmoBoxReloadable {}
class AmmoBoxWeapon : Firearm, IAmmoBoxReloadable {}

static class IClipReloadExtension {
    public static void Reload(this IClipReloadable firearm, Clip ammo) {}
}

static class IAmmoBoxReloadExtension {
    public static void Reload(this IAmmoBoxReloadable firearm, AmmoBox ammo) {}
}

因此,您将有两个定义的Reload()方法,其中Clip和AmmoBox作为ClipWeapon中的参数,而AmmoBoxWeapon类中只有1个Reload()方法,带有AmmoBox参数。

var ammoBox = new AmmoBox();
var clip = new Clip();

var clipWeapon = new ClipWeapon();
clipWeapon.Reload(ammoBox);
clipWeapon.Reload(clip);

var ammoBoxWeapon = new AmmoBoxWeapon();
ammoBoxWeapon.Reload(ammoBox);

如果您尝试将Clip传递给AmmoBoxWeapon.Reload,您将收到错误:

ammoBoxWeapon.Reload(clip); // <- ERROR at compile time

答案 3 :(得分:1)

我认为,检查是否完全正常,是否传递Ammo是有效类型。类似的情况是,当函数接受Stream但内部检查时,它是否可搜索或可写 - 取决于其要求。