2009-03-22 6 views
3

Angenommen, ich habe eine Sammlung von Objekten, die alle von einer Basisklasse erben. So etwas wie ...Wie ändere ich diesen Entwurf, um einen Downcast zu vermeiden?

abstract public class Animal 
    { 

    } 

    public class Dog :Animal 
    { 

    } 

    class Monkey : Animal 
    { 

    } 

Nun müssen wir diese Tiere füttern, aber sie sind nicht zu wissen erlaubt, wie sie sich zu ernähren. Wenn sie könnten, wäre die Antwort einfach sein:

foreach(Animal a in myAnimals) 
{ 
    a.feed(); 
} 

Allerdings können sie nicht wissen, wie sich selbst zu ernähren, so wollen wir so etwas wie dies tun:

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Animal> myAnimals = new List<Animal>(); 

     myAnimals.Add(new Monkey()); 
     myAnimals.Add(new Dog()); 

     foreach (Animal a in myAnimals) 
     { 
      Program.FeedAnimal(a); 
     } 
    } 

    void FeedAnimal(Monkey m) { 
     Console.WriteLine("Fed a monkey."); 
    } 

    void FeedAnimal(Dog d) 
    { 
     Console.WriteLine("Fed a dog."); 
    } 

} 

Natürlich, das gewonnen nicht kompilieren, da es einen Downcast erzwingen würde.

Es fühlt sich an, als ob es ein Designmuster oder eine andere Lösung mit Generika gibt, die mir aus diesem Problem helfen, aber ich habe noch nicht meine Finger darauf gelegt.

Vorschläge?

Antwort

7

Ein geprüfter Downcast, wie er in Linq Idiomen verwendet wird, ist vollkommen sicher.

foreach (Monkey m in myAnimals.OfType<Monkey>()) 
    Program.FeedAnimal(m); 

Oder Sie könnten das Besuchermuster verwenden. Das Besucherobjekt kennt alle Arten von Tieren, also hat es für jedes eine FeedAnimal Funktion. Sie übergeben das Besucherobjekt an die Feed-Funktion eines Tieres und rufen die korrekte FeedAnimal-Methode auf, wobei this übergeben wird.

Um es erweiterbar zu machen, müssen Sie eine Dictionary von Tierfutter:

private static Dictionary<Type, Action<Animal>> _feeders; 

eine Förderwirkung registrieren, können Sie dies mit beginnen tun würde:

_feeders[typeof(Monkey)] = 
    a => 
    { 
     Monkey m = (Monkey)a; 

     // give food to m somehow 
    }; 

Aber es gibt einen niedergeschlagenen , und Sie müssen auch den richtigen Typ in den Schlüssel eingeben. So einen Helfer machen:

public static void AddFeeder<TAnimal>(Action<TAnimal> feeder) where TAnimal : Animal 
{ 
    _feeders[typeof(TAnimal)] = a => feeder((TAnimal)a); 
} 

Dies das Muster erfaßt, so dass Sie es vollständig sicher wiederverwenden können:

AddFeeder<Monkey>(monkey => GiveBananasTo(monkey)); 
AddFeeder<Dog>(dog => ThrowBiscuitsAt(dog)); 

Dann, wenn Sie ein Tier müssen füttern, diese Erweiterung Methode:

public static void Feed(this Animal a) 
{ 
    _feeders[a.GetType()](a); 
} 

z

a.Feed(); 

So _feeders würde ein privates statisches Feld in der gleichen statischen Klasse, die als Erweiterungsmethode, zusammen mit dem AddFeeder Verfahren.

Update: der gesamte Code an einem Ort, auch mit Unterstützung für die Vererbung:

public static class AnimalFeeding 
{ 
    private static Dictionary<Type, Action<Animal>> _feeders 
     = new Dictionary<Type, Action<Animal>>(); 

    public static void AddFeeder<TAnimal>(Action<TAnimal> feeder) 
     where TAnimal : Animal 
    { 
     _feeders[typeof(TAnimal)] = a => feeder((TAnimal)a); 
    } 

    public static void Feed(this Animal a) 
    { 
     for (Type t = a.GetType(); t != null; t = t.BaseType) 
     { 
      Action<Animal> feeder; 
      if (_feeders.TryGetValue(t, out feeder)) 
      { 
       feeder(a); 
       return; 
      } 
     } 

     throw new SystemException("No feeder found for " + a.GetType()); 
    } 
} 

Sie auch eine Schleife durch die Schnittstellen Unterstützung von jedem Typ t haben könnte - im Grunde die gleiche Idee so bin ich Ich habe das Beispiel hier einfach gehalten.

+0

(seit Anforderung) Die 'OfType' ist in Ordnung, aber Sie müssten so viele' OfType's wie Sie Rassen haben - so wird Visitation schließlich. Die Wörterbuch/Delegierten-basierte Besucher-Implementierung, die hier diskutiert wird, ist einer von vielen gültigen Ansätzen, müsste aber ein wenig über die Vererbung nachdenken ... –

+0

... d. Wenn ich eine Aktion für "Hund" registriert habe, aber eine "Collie" -Instanz hinzufüge, würde sie brechen. Auch die Lambdas lassen sich (glaube ich) zu AddFeeder (GiveBananasTo) vereinfachen; AddFeeder (ThrowBiscuitsAt). Aber eine vernünftige Diskussion; - = p –

+0

Der OfType, den ich wirklich erwähnt habe, um zu zeigen, wie ein Downcast in Ordnung ist, solange Sie es einkapseln, um es sicher zu machen, als ein Intro für den Rest der Antwort. –

0

Da Tiere sich selbst oder andere Tiere nicht füttern dürfen, haben Sie keine andere Möglichkeit, eine Klasse "Besitzer" oder "Keeper" oder "Hausmeister" zu erstellen, die eine private Methode zur Tierfütterung besitzt.

1

Dies ist ein klassisches Problem in OOD. Wenn Ihre Gruppe von Klassen (Tieren) festgelegt ist, können Sie die visitor pattern verwenden. Wenn Ihre Menge an Aktionen (zB Feed) begrenzt ist, fügen Sie einfach einen Methoden-Feed() zu Animal hinzu. Wenn nichts davon zutrifft, gibt es keine einfache Lösung.

1

Generics wäre nur hilfreich, wenn Sie eine "Liste von Hunden" hätten und eine Methode mit einem Argument aufrufen wollten, das eine "Liste von Dingen ist, die Tiere sind" (zB List<T> where T : Animal) - ich glaube nicht, dass es hier hilft .

Ich vermute, Sie werden eine visitor pattern ... einige Reihe von Objekten müssen, die wissen, wie eine bestimmte Art von Tier zu füttern, und halten Sie sie versuchen, bis Sie einen finden, der weiß, wie ...

+0

Würde mich interessieren, Ihre Kommentare zu meiner Antwort zu hören, Marc. –

0

Sie könnten eine Elementvariable in die Tierklasse aufnehmen, die den Typ des Tieres identifizieren würde und dann die Feed-Funktion lesen lassen und daraus verschiedene Ergebnisse erzeugen würde.

2

Zunächst einmal ist einer der Hauptpunkte des objektorientierten Designs, dass Objekte ihre Daten mit dem Verhalten bündeln, das auf diese Daten wirkt (d. H. "Tiere wissen, wie sie sich selbst ernähren"). Also das ist einer von denen "Doktor, es tut weh, wenn ich das tue! - Also mach das nicht" Situationen.

Das sagte, ich bin sicher, es gibt mehr zu der Geschichte als Sie beschrieben haben und dass Sie gute Gründe haben, nicht in der Lage zu sein, "richtige" OOD zu tun. Du hast also ein paar Optionen.

Sie können Ihre FeedAnimal (Tier a) -Methode verwenden Reflexion, um den Typ des Tieres zu finden. Im Grunde machen Sie Ihren Polymorphismus in der FeedAnimal-Methode.

static void FeedAnimal(Animal a) 
{ 
    if (a is Dog) 
    { 
     Console.WriteLine("Fed a dog."); 
    } 
    else if (a is Monkey) 
    { 
     Console.WriteLine("Fed a monkey."); 
    } 
    else 
    { 
     Console.WriteLine("I don't know how to feed a " + a.GetType().Name + "."); 
    }  
} 

Ein objektorientierterer, aber komplizierterer Weg wäre, das von anderen vorgeschlagene Besuchermuster zu verwenden. Dies ist für den erfahrenen Entwickler eleganter, aber für weniger erfahrene Programmierer weniger offensichtlich und lesbar. Welchen Ansatz Sie bevorzugen, hängt davon ab, wie viele verschiedene Tierarten Sie haben.

+0

Sie können 'ist': a ist Hund, ein ist Affe ... – strager

+0

Oh, guter Punkt - dumm von mir, das zu vergessen. –

+0

+1 für Besuchermuster. –

Verwandte Themen