it-swarm.com.de

Wie gehe ich mit den Methoden um, die für Subtypen im Kontext des Polymorphismus hinzugefügt wurden?

Wenn Sie das Konzept des Polymorphismus verwenden, erstellen Sie eine Klassenhierarchie und rufen mithilfe der Elternreferenz die Schnittstellenfunktionen auf, ohne zu wissen, welcher bestimmte Typ das Objekt hat. Das ist toll. Beispiel:

Sie haben eine Sammlung von Tieren und rufen alle Tierfunktionen eat auf, und es ist Ihnen egal, ob es sich um einen fressenden Hund oder eine Katze handelt. Aber in derselben Klassenhierarchie haben Sie Tiere, die zusätzliche haben - außer geerbt und implementiert von der Klasse Animal, z. makeEggs, getBackFromTheFreezedState und so weiter. In einigen Fällen möchten Sie in Ihrer Funktion möglicherweise den spezifischen Typ kennen, um zusätzliche Verhaltensweisen aufzurufen.

Wenn es beispielsweise Morgen ist und es sich nur um ein Tier handelt, rufen Sie eat auf. Wenn es sich um einen Menschen handelt, rufen Sie zuerst washHands, getDressed und an Erst dann rufe eat auf. Wie gehe ich mit diesen Fällen um? Polymorphismus stirbt. Sie müssen den Typ des Objekts herausfinden, der wie ein Codegeruch klingt. Gibt es einen gemeinsamen Ansatz, um diese Fälle zu behandeln?

14
Narek

Hängt davon ab. Leider gibt es keine generische Lösung. Denken Sie über Ihre Anforderungen nach und versuchen Sie herauszufinden, was diese Dinge tun sollten.

Zum Beispiel sagten Sie am Morgen, dass verschiedene Tiere verschiedene Dinge tun. Wie wäre es, wenn Sie eine Methode getUp() oder prepareForDay() oder so etwas einführen. Dann können Sie mit dem Polymorphismus fortfahren und jedes Tier seine Morgenroutine ausführen lassen.

Wenn Sie zwischen Tieren unterscheiden möchten, sollten Sie sie nicht wahllos in einer Liste speichern.

Wenn nichts anderes funktioniert, können Sie das Besuchermuster ausprobieren, das Art von a ist hack , um eine Art dynamisches Dispatching zu ermöglichen, bei dem Sie einen Besucher einreichen können, der typgenaue Rückrufe von Tieren erhält. Ich möchte jedoch betonen, dass dies ein letzter Ausweg sein sollte, wenn alles andere fehlschlägt.

18

Dies ist eine gute Frage und es ist die Art von Ärger, die viele Leute haben, wenn sie versuchen zu verstehen, wie man OO benutzt. Ich denke, die meisten Entwickler haben damit zu kämpfen. Ich wünschte, ich könnte sagen, dass die meisten darüber hinwegkommen, aber ich bin mir nicht sicher, ob das der Fall ist. Nach meiner Erfahrung verwenden die meisten Entwickler Pseudo-OO-Eigenschaftstaschen .

Lassen Sie mich zunächst klar sein. Das ist nicht deine Schuld. Die Art und Weise, wie OO] normalerweise gelehrt wird, ist sehr fehlerhaft. Das Beispiel Animal ist der Haupttäter IMO. Grundsätzlich sagen wir, lassen Sie uns über Objekte sprechen, was sie tun können Animal kann eat() und es kann speak(). Super. Jetzt erstelle einige Tiere und codiere, wie sie essen und sprechen. Jetzt weißt du OO, richtig?

Das Problem ist, dass dies bei OO aus der falschen Richtung) geschieht. Warum gibt es Tiere in diesem Programm und warum müssen sie sprechen und essen?

Es fällt mir schwer, über eine echte Verwendung für einen Animal -Typ nachzudenken. Ich bin mir sicher, dass es existiert, aber lassen Sie uns etwas besprechen, über das ich meiner Meinung nach leichter nachdenken kann: eine Verkehrssimulation. Angenommen, wir möchten den Verkehr in verschiedenen Szenarien modellieren. Hier sind einige grundlegende Dinge, die wir brauchen, um das zu können.

Vehicle
Road
Signal

Wir können mit allen möglichen Dingen, Fußgängern und Zügen, tiefer gehen, aber wir werden es einfach halten.

Betrachten wir Vehicle. Welche Fähigkeiten benötigt das Fahrzeug? Es muss auf einer Straße fahren. Es muss in der Lage sein, bei Signalen anzuhalten. Es muss in der Lage sein, Kreuzungen zu navigieren.

interface Vehicle {
  move(Road road);
  navigate(Road... intersection);
}

Das ist wahrscheinlich zu einfach, aber es ist ein Anfang. Jetzt. Was ist mit all den anderen Dingen, die ein Fahrzeug tun könnte? Sie können von einer Straße in einen Graben abbiegen. Ist das Teil der Simulation? Nein, brauche es nicht. Einige Autos und Busse verfügen über eine Hydraulik, mit der sie hüpfen bzw. knien können. Ist das Teil der Simulation? Nein, brauche es nicht. Die meisten Autos verbrennen Benzin. Einige tun es nicht. Ist das Kraftwerk Teil der Simulation? Nein, brauche es nicht. Radgröße? Ich brauche es nicht. GPS Navigation? Infotainmentsystem? Ich brauche sie nicht.

Sie müssen nur Verhaltensweisen definieren, die Sie verwenden möchten. Zu diesem Zweck denke ich, dass es oft besser ist, OO) Schnittstellen aus dem Code zu erstellen, der mit ihnen interagiert. Sie beginnen mit einer leeren Schnittstelle und schreiben dann den Code, der die nicht vorhandenen Methoden aufruft. Auf diese Weise wissen Sie, welche Methoden Sie für Ihre Benutzeroberfläche benötigen. Sobald Sie dies getan haben, definieren Sie Klassen, die diese Verhaltensweisen implementieren. Nicht verwendete Verhaltensweisen sind irrelevant und müssen nicht definiert werden.

Der springende Punkt bei OO ist, dass Sie später neue Implementierungen dieser Schnittstellen hinzufügen können, ohne den aufrufenden Code zu ändern. Die einzige Möglichkeit, die funktioniert, besteht darin, dass die Anforderungen des aufrufenden Codes bestimmen, was in der Schnittstelle enthalten ist Es gibt keine Möglichkeit, alle Verhaltensweisen aller möglichen Dinge zu definieren, die später erfunden werden könnten.

33
JimmyJames

TL; DR:

Denken Sie an eine Abstraktion und Methoden, die für alle Unterklassen gelten und alles abdecken, was Sie benötigen.

Bleiben wir zunächst bei Ihrem Beispiel eat().

Es ist eine Eigenschaft, ein Mensch zu sein, dass Menschen als Voraussetzung für das Essen ihre Hände waschen und sich vor dem Essen anziehen wollen. Wenn Sie möchten, dass jemand zu Ihnen kommt, um mit Ihnen zu frühstücken, müssen Sie nicht tell ihre Hände waschen und sich anziehen, sondern sie tun es selbst, wenn Sie sie einladen oder sie antworten "Nein, ich kann nicht rüberkommen, ich habe meine Hände nicht gewaschen und ich bin noch nicht angezogen".

Zurück zur Software:

Da eine Human -Instanz ohne die Voraussetzungen nicht essen kann, muss die Methode eat() von HumanwashHands() und getDressed() if ausführen das wurde noch nicht gemacht. Es sollte nicht Ihre Aufgabe als eat() -Aufrufer sein, über diese Besonderheit Bescheid zu wissen. Die Alternative des hartnäckigen Menschen wäre, eine Ausnahme zu machen ("Ich bin nicht bereit zu essen!"), Wenn die Voraussetzungen nicht erfüllt sind, was Sie frustriert, aber zumindest darüber informiert, dass das Essen nicht funktioniert.

Was ist mit makeEggs()?

Ich würde empfehlen, Ihre Denkweise zu ändern. Sie möchten wahrscheinlich die geplanten Morgenaufgaben aller Wesen ausführen. Auch hier sollte es als Anrufer nicht Ihre Aufgabe sein, zu wissen, welche Aufgaben sie haben. Daher würde ich eine doMorningDuties() -Methode empfehlen, die alle Klassen implementieren.

9
Ralf Kleberhoff

Die Antwort ist ganz einfach.

Wie gehe ich mit Objekten um, die mehr können als Sie erwarten?

Sie müssen nicht damit umgehen, da dies keinen Zweck erfüllen würde. Eine Schnittstelle wird normalerweise abhängig von der Art und Weise entworfen, wie sie verwendet wird. Wenn Ihre Schnittstelle kein Händewaschen definiert, ist es Ihnen als Schnittstellenanrufer egal. Wenn Sie das getan hätten, hätten Sie es anders gestaltet.

Wenn es beispielsweise Morgen ist und es sich nur um ein Tier handelt, rufen Sie eat an. Wenn es sich um einen Menschen handelt, rufen Sie zuerst washHands, getDressed und erst dann eat auf. Wie gehe ich mit diesen Fällen um?

Zum Beispiel im Pseudocode:

interface IEater { void Eat(); }
interface IMorningRoutinePerformer { void DoMorningRoutine(); }
interface IAnimal : IEater, IMorningPerformer;
interface IHuman : IEater, IMorningPerformer; 
{
  void WashHands();
  void GetDressed();
}

void MorningTime()
{
   IList<IMorningRoutinePerformer> items = Service.GetMorningPerformers();
   foreach(item in items) { item.DoMorningRoutine(); }
}

Jetzt implementieren Sie IMorningPerformer für Animal, um nur zu essen, und für Human implementieren Sie es auch, um Hände zu waschen und sich anzuziehen. Der Anrufer Ihrer MorningTime-Methode könnte sich weniger darum kümmern, ob es sich um einen Menschen oder ein Tier handelt. Alles, was es will, ist die Morgenroutine, die jedes Objekt dank OO bewundernswert macht.

Polymorphismus stirbt.

Oder doch?

Sie müssen den Typ des Objekts herausfinden

Warum nehmen wir das an? Ich denke, das könnte eine falsche Annahme sein.

Gibt es einen gemeinsamen Ansatz, um diese Fälle zu behandeln?

Ja, normalerweise wird es mit einer sorgfältig entworfenen Klassen- oder Schnittstellenhierarchie gelöst. Beachten Sie, dass im obigen Beispiel nichts Ihrem Beispiel widerspricht, wie Sie es angegeben haben. Sie werden sich jedoch wahrscheinlich unzufrieden fühlen, da Sie einige weitere Annahmen getroffen haben, die Sie zum Zeitpunkt des Schreibens nicht in die Frage geschrieben haben und diese Annahmen werden wahrscheinlich verletzt.

Es ist möglich, zu einem Kaninchenbau zu gehen, indem Sie Ihre Annahmen verschärfen und ich die Antwort ändere, um sie immer noch zu befriedigen, aber ich denke nicht, dass dies nützlich wäre.

Das Entwerfen guter Klassenhierarchien ist schwierig und erfordert viel Einblick in Ihre Geschäftsdomäne. Bei komplexen Domänen werden zwei, drei oder sogar mehr Iterationen durchgeführt, um das Verständnis für die Interaktion verschiedener Entitäten in ihrer Geschäftsdomäne zu verfeinern, bis ein angemessenes Modell gefunden wird.

Hier fehlen vereinfachende Tierbeispiele. Wir möchten einfach unterrichten, aber das Problem, das wir zu lösen versuchen, ist nicht offensichtlich, bis Sie tiefer gehen, dh komplexere Überlegungen und Bereiche haben.

2
Andrew Savinykh