我陷入了这种情况:
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
。
或者,我可以从Reload
移除Firearm
,并将ClipWeapon
和ShellWeapon
与我需要的特定参数一起删除,但这样做会失去优势多态性,这不是我想要的。
如果我可以覆盖Reload
内的ShellWeapon
,那么这不是最佳选择:
public override void Reload(AmmoBox ammoBox)
{
// reload ...
}
当然我试过了,它没有用,我收到一个错误,说签名必须匹配或者其他什么,但这不应该是'逻辑'有效吗?由于AmmoBox
是Ammo
?
我该如何解决这个问题?一般来说,我的设计是否正确?
(注意我使用的是接口IClipWeapon
和IShellWeapon
,但是我遇到了麻烦,所以我转而使用了类)
提前致谢。
答案 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
但内部检查时,它是否可搜索或可写 - 取决于其要求。