是否可以使用派生参数而不是基础参数覆盖方法?
我陷入了这种情况:
- 我有一个名为
Ammo
的抽象类,AmmoBox
和Clip
作为子项。 - 我有一个名为
Weapon
的抽象类,Firearm
和Melee
是孩子。 -
Firearm
是抽象的,ClipWeapon
和ShellWeapon
是孩子。 - 在
Firearm
内部,有一个void Reload(Ammo ammo);
问题是, ClipWeapon
可以使用Clip
和AmmoBox
重新加载:
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
。
或者,我可以从Firearm
移除Reload
,并将它与ClipWeapon
和ShellWeapon
一起放入我需要的特定参数,但这样做我将失去多态性的好处,这不是我想要的。
如果我可以覆盖ShellWeapon
Reload
,那么这不是最佳选择:
public override void Reload(AmmoBox ammoBox) { // reload ... }
当然我试过了,它没有用,我收到一个错误,说签名必须匹配或者其他什么,但这不应该是’逻辑’有效吗? 既然AmmoBox
是Ammo
?
我应该怎么解决这个问题? 一般来说,我的设计是否正确? (注意我使用的是接口IClipWeapon
和IShellWeapon
但我遇到了麻烦,所以我转而使用类代替)
提前致谢。
但这不应该是’逻辑’有效吗?
不。你的界面说调用者可以传入任何 Ammo
– 你限制它需要AmmoBox
,这是更具体的。
如果有人要写,你会发生什么?
Firearm firearm = new ShellWeapon(); firearm.Reload(new Ammo());
? 那应该是完全有效的代码 – 所以你想让它在执行时爆炸吗? 静态类型的一半是避免这种问题。
您可以在弹药类型中使用Firearm
generics:
public abstract class Firearm : Weapon where TAmmo : Ammo { public abstract void Reload(TAmmo ammo); }
然后:
public class ShellWeapon : Firearm
这可能是也可能不是一种有用的做事方式,但至少值得考虑。
您可以使用带有接口扩展的合成而不是多重inheritance:
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
你摔跤的问题来自于需要根据弹药和武器的运行时类型调用不同的实现。 本质上,重新加载的动作需要相对于两个而不是一个对象是“虚拟的”。 这个问题叫做双重调度 。
解决它的一种方法是创建一个类似访客的结构:
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
,而不是决定他们得到什么样的弹药。 AddTo...Weapon
方法既知道自己的类型,也知道重新加载武器的类型。
我认为,检查是否完全正常,是否通过Ammo
是有效的类型。 类似的情况是,当函数接受Stream
,但在内部检查它是否可搜索或可写 – 取决于它的要求。