15

我陷入了这种情况:

  1. 我有一个抽象类,叫做Ammo, with AmmoBoxand Clipas children。
  2. 我有一个抽象类,叫做Weapon, with Firearmand Meleeas children。
  3. Firearm是抽象的,有孩子,ClipWeaponShellWeapon有孩子。
  4. 里面Firearm,有一个void Reload(Ammo ammo);

问题是,aClipWeapon可以同时使用 aClip和 anAmmoBox来重新加载:

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

但是 a ShellWeapon, 只能使用 anAmmoBox来重新加载。我可以这样做:

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

但这很糟糕,因为即使我正在检查以确保它是 type AmmoBox,但从外面看,似乎 aShellWeapon也可以采用 a Clip,因为 aClip也是Ammo

或者,我可以从 中删除ReloadFirearm并同时ClipWeapon使用ShellWeapon我需要的特定参数,但这样做我将失去多态性的好处,这不是我想要的。

如果我可以像这样覆盖Reload内部,那不是最佳选择ShellWeapon

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

当然我试过了,但它没有用,我收到一个错误,说签名必须匹配或其他东西,但这不应该是“逻辑上”有效的吗?因为AmmoBox是一个Ammo

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

提前致谢。

4

4 回答 4

19

但这不应该是“逻辑上”有效的吗?

不,您的界面说调用者可以传入任何 Ammo- 您将其限制为 require 的地方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>

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

于 2013-07-30T10:22:35.783 回答
3

您正在努力解决的问题来自需要根据弹药武器的运行时类型调用不同的实现。本质上,重新加载的动作需要是“虚拟”的,相对于两个,而不是一个,对象。这个问题称为双重分派

解决它的一种方法是创建一个类似访问者的构造:

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方法知道他们自己的类型,以及他们正在重新加载的武器的类型。

于 2013-07-30T10:29:03.267 回答
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) {}
}

这样您将有 2 个 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
于 2013-07-30T10:39:59.700 回答
1

我认为,检查 pass 是否Ammo是有效类型是完全可以的。类似的情况是,当函数接受 a 时Stream,但在内部检查它是可搜索的还是可写的 - 取决于它的要求。

于 2013-07-30T10:22:45.730 回答