2013-07-30 5 views
10

Ich bin in dieser Situation stecken, wo:Kann eine Methode mit einem abgeleiteten Parameter anstelle einer Basis überschrieben werden?

  1. Ich habe eine abstrakte Klasse Ammo, mit AmmoBox und Clip als Kinder bezeichnet.
  2. Ich habe eine abstrakte Klasse namens Weapon, mit Firearm und Melee als Kinder.
  3. Firearm ist abstrakt, mit ClipWeapon und ShellWeapon als Kinder.
  4. Innen Firearm, gibt es ein void Reload(Ammo ammo);

Das Problem ist, dass ein ClipWeapon verwenden könnte sowohl ein Clip und ein AmmoBox neu zu laden:

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

Aber ein ShellWeapon, nur ein AmmoBox nutzen könnte, um neu laden. Ich kann dies tun:

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

Aber das ist schlecht, weil, obwohl ich überprüfe, um sicherzustellen, es ist vom Typ AmmoBox, von außen scheint es wie ein ShellWeapon auch ein Clip nehmen konnte, da ein Clip ist Ammo auch.

Oder ich Reload von Firearm entfernen konnte, und lege sie sowohl ClipWeapon und ShellWeapon mit der spezifischen params ich brauche, aber tun, damit ich die Vorteile von Polymorphismus verlieren, was nicht das, was ich will.

Wäre es nicht optimal sein, wenn ich Reload innen ShellWeapon wie dies außer Kraft setzen könnte:

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

Natürlich habe ich es versucht, und es hat nicht funktioniert, habe ich eine Fehlermeldung, die Signatur übereinstimmen muss oder etwas, aber sollte das nicht "logisch" gelten? seit AmmoBox ist ein Ammo?

Wie soll ich das umgehen? Und ist mein Design im Allgemeinen korrekt? (Hinweis: Ich verwendete die Schnittstellen IClipWeapon und IShellWeapon, aber ich lief in Schwierigkeiten, so zog ich stattdessen Klassen verwenden)

Vielen Dank im Voraus.

Antwort

13

but shouldn't this be valid 'logically'?

Nein, Ihre Schnittstelle sagt, dass der Anrufer in jedeAmmo passieren kann - wo Sie es sind Einschränkung eine AmmoBox zu verlangen, die mehr spezifisch ist.

Was möchten Sie passieren erwarten, wenn jemand schreiben würde:

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

? Das sollte ein absolut gültiger Code sein - also möchtest du, dass er zur Ausführungszeit in die Luft geht? Der halbe Punkt der statischen Typisierung besteht darin, diese Art von Problem zu vermeiden.

Sie könnte machen Firearm Generika in der Art der Munition ist verwendet:

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

Dann:

public class ShellWeapon : Firearm<AmmoBox> 

Das mag oder nicht eine nützliche Art und Weise, Dinge zu tun sein kann, aber es ist zumindest eine Überlegung wert.

+0

Wie kann die allgemeine Definition neu schreiben Sie schrieb aber für Firearm statt Waffe? (weil Shell und Clip Waffen von Firearm und nicht Weapon direkt erben) Ich habe sowas versucht, aber offensichtlich ist es das nicht: public abstract class Schusswaffe wo Munition: Munition: Waffe XD – vexe

+0

@VeXe: Du musst die ': Waffe einsetzen 'vor der' wo AmmoType: Ammo'. Ich werde meine Antwort aktualisieren, um dies zu zeigen. Ich würde Ihnen jedoch dringend empfehlen, das T-Präfix für Typparameter zu verwenden. –

+0

Ich denke, das ist das Beste, aber darf ich fragen, warum hast du gesagt, dass es keine nützliche Methode ist, Dinge zu tun? – vexe

1

Ich denke, dass es vollkommen in Ordnung ist zu überprüfen, ob bestanden Ammo von gültigem Typ ist. Die ähnliche Situation ist, wenn Funktion akzeptiert Stream, aber intern überprüft, ob es suchbar oder beschreibbar ist - je nach seinen Anforderungen.

3

Das Problem, mit dem Sie kämpfen, kommt von der Notwendigkeit, eine andere Implementierung basierend auf den Laufzeittypen der Munition und der Waffe aufzurufen. Im Wesentlichen muss die Nachladeaktion "virtuell" in Bezug auf zwei, nicht auf ein Objekt sein. Dieses Problem wird double dispatch genannt.

Eine Möglichkeit, zu adressieren es einen Besucher artiges Konstrukt zu schaffen würde:

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

„Die Magie“ in den Implementierungen von Reload der Waffe Subklassen geschieht: anstatt zu entscheiden, welche Art von Munition sie bekommen, sie lassen die Munition selbst "das zweite Bein" der doppelten Disposition machen und nennen, was auch immer angebracht ist, weil ihre Methoden sowohl ihren eigenen Typ als auch den Typ der Waffe, in die sie geladen werden, kennen.

+0

Das Problem ist, da sowohl AddToShellWeapon als auch AddToClipWeapon beide abstrakt sind, müssen sie in jedem, der Ammo erbt, definiert werden, also gibt es innerhalb von Clip AddToShellWeapon, das redundant ist. Korrigiere mich, wenn ich falsch liege. – vexe

+0

@VeXe Sie müssen sie nicht abstrakt machen - Sie können sie virtuell machen und sie standardmäßig eine Ausnahme auslösen lassen, indem sie sagen "diese Art von Munition kann nicht zu dieser Art von Waffe hinzugefügt werden". Ich habe die Antwort bearbeitet, um den Ansatz zu veranschaulichen. – dasblinkenlight

+0

Es ist eine gute Idee, in die andere Richtung zu gehen, aber wenn ich das tun würde, warum würde ich dann nicht einfach meine erste Lösung verwenden und eine andere hinzufügen, wenn (Munition ist Clip) -> Ausnahme werfen? Was liefert Ihre Methode mehr, so dass ich sie anstelle meiner betrachten würde? – vexe

2

Sie können Komposition mit Schnittstellenerweiterungen statt Mehrfachvererbung verwenden:

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

Damit Sie 2 Definitionen von Reload() -Methode mit Clip und Ammobox als Argumente in ClipWeapon haben und nur 1 Reload() Methode in der AmmoBoxWeapon-Klasse mit dem AmmoBox-Argument.

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

Und wenn Sie Pass Clip zu AmmoBoxWeapon.Reload versuchen wird eine Fehlermeldung angezeigt:

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

Das ist genau das, was ich versuche, aber die Art und Weise, wie Sie es tun, ist ein wenig jenseits meines Wissens. Ich habe nicht wirklich verstanden, was mit den Extensions los ist und was das "Das" macht ... Vielleicht schaue ich mir ein paar Tutorials zu dieser Interface-Erweiterung an und schaue mir deine Lösung noch einmal an. Was soll in die Schnittstellen passen? Tut mir leid, aber im Moment ist es für mich etwas verschwommen. – vexe

+0

@VeXe Wenn Sie nur die Reload-Methode benötigen, können Sie Schnittstellen leer lassen, die Erweiterung wird alles tun, was Sie brauchen. Schau sie dir an, es ist eine nützliche Funktion von C# für die funktionale Erweiterung von Klassen. –

+0

@Rinold das ist schön, danke – user25064

Verwandte Themen