2013-07-30 29 views
10

我被困在這種情況下:是否有可能重寫與推導出的參數,而不是一個基地之一的方法?

  1. 我有一個叫Ammo抽象類,與AmmoBoxClip兒童。
  2. 我有一個叫Weapon抽象類,有FirearmMelee兒童。
  3. Firearm是抽象的,與ClipWeaponShellWeapon兒童。
  4. Firearm,有一個void Reload(Ammo ammo);

的問題是,一個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爲好,因爲ClipAmmo爲好。

或者,我可以從Firearm刪除Reload,並把它都ClipWeaponShellWeapon與我所需要的具體參數,可以但這樣做我會失去多態性的好處,這不是我想要的。

豈不是最佳的,如果我能覆蓋ReloadShellWeapon這樣的:

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

當然,我試了一下,並沒有工作,我得到了一個錯誤說的簽名必須匹配或什麼的,但不應該這是'邏輯'有效嗎?因爲AmmoBoxAmmo

我應該如何解決這個問題?一般來說,我的設計是否正確? (注意我使用的接口是IClipWeaponIShellWeapon但我遇到了麻煩,所以我轉而使用類代替)

在此先感謝。

回答

13

but shouldn't this be valid 'logically'?

號你的接口說,來電者可以在任何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> 

,可能會或可能不會做事情的有效方式,但它的至少值得考慮。

+0

如何重新寫你寫但火器,而不是武器的一般定義是什麼? (因爲Shell和Clip武器是繼承自火器而不是武器直接)我嘗試了類似的東西,但顯然不是這樣的:public abstract class Firearm 其中AmmoType:彈藥:Weapon XD – vexe

+0

@VeXe:你需要把':Weapon '在'AmmoType:Ammo'之前。我會更新我的答案來證明這一點。我會*強烈*建議你使用'T'前綴類型參數。 –

+0

我認爲這是最好的,但我可以問爲什麼你說這可能不是一種有用的做事方式? – vexe

1

我認爲,檢查通過Ammo是否是有效的類型是完全正確的。類似的情況是,當函數接受Stream時,但在內部檢查它是可尋址還是可寫 - 取決於它的要求。

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方法知道它們自己的類型以及它們被重新加載到的武器的類型。

+0

問題是,既然AddToShellWeapon和AddToClipWeapon都是抽象的,它們必須在繼承Ammo的人中定義,所以在Clip內部有AddToShellWeapon,這是多餘的。如我錯了請糾正我。 – vexe

+0

@VeXe你不需要讓它們變成抽象的 - 你可以讓它們變成虛擬的,並且讓它們默認拋出一個異常,並說「這種無用武器不能被添加到那種武器中」。我編輯了答案來說明方法。 – dasblinkenlight

+0

這是一個不錯的主意,以另一種方式,但如果我這樣做,那麼爲什麼我不使用我的第一個解決方案,並添加一個其他的if(彈藥是剪輯) - >拋出異常?你的方法提供了多少,所以我會考慮它而不是我的? – vexe

2

您可以使用接口擴展組成,而不是多重繼承:

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

所以,你將不得不重新加載()方法的夾子與AmmoBox在ClipWeapon參數,只有1重載的2所定義()方法在AmmoBoxWeapon類中使用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); 

如果你嘗試通過剪輯來AmmoBoxWeapon.Reload你會得到一個錯誤:

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

這完全是我想要做的,但你做這件事的方式有點超出我的知識範圍。我並沒有真正瞭解這些擴展的功能,以及'這個'正在做什麼......也許我會去檢查一下這個接口擴展的一些教程,並再次看看你的解決方案。接口內應該適合什麼?對不起,現在對我來說有點模糊。 – vexe

+0

@VeXe如果您只需要重新加載方法,您可以將接口保留爲空,擴展將完成您所需的一切。看看它們,這是C#功能擴展類的非常有用的特性。 –

+0

@Rinold這是美麗的,謝謝 – user25064

相關問題