Wie wird Polymorphismus in der realen Welt eingesetzt? [geschlossen]

17

Ich versuche zu verstehen, wie Polymorphismus in einem realen Projekt verwendet wird, aber ich kann nur das klassische Beispiel (oder etwas Ähnliches) für eine AnimalElternklasse mit einer Methode speak()und viele Kindklassen finden, die diese Methode außer Kraft setzen Sie können die Methode speak()für jedes der untergeordneten Objekte aufrufen , zum Beispiel:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();
Christopher
quelle
1
Die Sammlungen, die Sie jeden Tag sehen und benutzen, reichen aus, um zu verstehen, was Polymorphismus ist. Aber wie man Polymorphismus effektiv beim Lösen von Problemen einsetzt, ist eine Fähigkeit, die man meistens durch Erfahrung und nicht nur durch Diskussion erlangt. Mach weiter und mach dir die Hände schmutzig.
Durgadass S
Wenn Sie über eine Reihe von Typen verfügen, die alle eine Art Mindestschnittstelle unterstützen (z. B. eine Reihe von Objekten, die gezeichnet werden müssen), eignet sich eine Schnittstelle in der Regel gut, um die Unterschiede zwischen Objekten aus dem Aufruf zum Zeichnen auszublenden. Auch wenn Sie eine API mit Methoden erstellen (oder damit arbeiten), die ein Basisobjekt und eine erhebliche Anzahl von Typen bedienen können, die mehr oder weniger auf dieselbe Weise davon erben , ist Polymorphismus möglicherweise die beste Methode, um die Unterschiede zwischen diesen zu abstrahieren diese Typen.
jrh
Im Allgemeinen if(x is SomeType) DoSomething()kann es sich lohnen, Polymorphismus zu verwenden, wenn Sie häufig überladene Methoden ausführen, um mit verschiedenen Typen umzugehen, und wenn der Code ähnlich ist, oder wenn Sie häufig schreiben . Für mich ist Polymorphismus eine Entscheidung, die dem Zeitpunkt ähnelt, an dem eine separate Methode erstellt werden soll. Wenn ich feststelle, dass ich den Code einige Male wiederholt habe, baue ich ihn normalerweise in eine Methode if object is this type do thisum Refactoring und Hinzufügen einer Schnittstelle oder Klasse.
jrh

Antworten:

35

Stream ist ein großartiges Beispiel für Polymorphismus.

Stream repräsentiert eine "Folge von Bytes, die gelesen oder geschrieben werden können". Diese Sequenz kann jedoch aus einer Datei, aus dem Speicher oder aus vielen Arten von Netzwerkverbindungen stammen. Oder es kann als Dekorator dienen, der vorhandene Datenströme umschließt und die Bytes auf eine Art und Weise umwandelt, beispielsweise durch Verschlüsselung oder Komprimierung.

Auf diese Weise muss sich der Client, der Stream verwendet, nicht darum kümmern, woher die Bytes kommen. Nur damit sie der Reihe nach gelesen werden können.

Einige würden sagen Stream, dass es sich um ein falsches Beispiel für Polymorphismus handelt, da es viele "Funktionen" definiert, die von den Implementierungsprogrammen nicht unterstützt werden, z. Oder mangelndes Suchen. Dies ist jedoch nur eine Frage der Komplexität, da Streamsie in viele Teile unterteilt werden kann, die unabhängig voneinander implementiert werden könnten.

Euphorisch
quelle
2
In Sprachen mit mehrere und virtueller Vererbung wie C ++ dieses Beispiel kann den „gefürchteten Diamant“ -Muster auch zeigen ... von Klassen Eingangs- und Ausgangsstrom von einer Basisstrom - Klasse abgeleitet, und ich sowohl einen I / O - Stream erstellen
gyre
2
@gyre Und gut gemacht, es gibt keinen Grund, das Rautenmuster zu „fürchten“. Es ist wichtig, sich des Gegenteils im Diamanten bewusst zu sein und keine Namenskonflikte damit zu verursachen, und es ist eine Herausforderung und ärgerlich und ein Grund, das Diamantenmuster zu vermeiden, wo dies praktikabel ist Zum Beispiel könnte eine Namenskonvention die Probleme lösen.
KRyan
+1 Streams sind mein absolutes Lieblingsbeispiel für Polymorphismus. Ich versuche nicht einmal mehr, den Menschen das fehlerhafte Tier-, Säugetier- und Hundemodell beizubringen Stream.
Pharap
@KRyan Ich habe meine eigenen Gedanken nicht ausgedrückt, indem ich sie "gefürchteter Diamant" nannte. Ich habe gerade gehört, dass sie als solche bezeichnet wird. Ich stimme vollkommen zu; Ich denke, es ist etwas, das jeder Entwickler in der Lage sein sollte, seinen Kopf herumzureißen und angemessen zu verwenden.
Gyre
@gyre Oh ja, das habe ich tatsächlich verstanden. Aus diesem Grund habe ich mit „und“ begonnen, um anzuzeigen, dass dies eher eine Erweiterung Ihres Gedankens als ein Widerspruch war.
KRyan
7

Ein typisches spielebezogenes Beispiel wäre eine Basisklasse Entity, die gemeinsame Mitglieder wie draw()oder bereitstellt update().

Für ein reineres datenorientiertes Beispiel könnte es eine Basisklasse geben, Serializabledie ein gemeinsames saveToStream()und ein loadFromStream().

Mario
quelle
6

Es gibt verschiedene Arten von Polymorphismus, wobei derjenige von Interesse normalerweise Laufzeitpolymorphismus / dynamischer Versand ist.

Eine sehr allgemeine Beschreibung des Laufzeitpolymorphismus ist, dass ein Methodenaufruf abhängig vom Laufzeittyp seiner Argumente unterschiedliche Aktionen ausführt: Das Objekt selbst ist für die Auflösung eines Methodenaufrufs verantwortlich. Dies ermöglicht eine enorme Flexibilität.

Eine der gebräuchlichsten Möglichkeiten, diese Flexibilität zu nutzen, ist die Abhängigkeitsinjektion , z. B. um zwischen verschiedenen Implementierungen zu wechseln oder Scheinobjekte zum Testen zu injizieren. Wenn ich im Voraus weiß, dass es nur eine begrenzte Anzahl von Auswahlmöglichkeiten gibt, könnte ich versuchen, sie mit Bedingungen fest zu codieren, zB:

void foo() {
  if (isTesting) {
    ... // do mock stuff
  } else {
    ... // do normal stuff
  }
}

Dies macht es schwierig, dem Code zu folgen. Die Alternative besteht darin, eine Schnittstelle für diese Foo-Operation einzuführen und eine normale Implementierung und eine Scheinimplementierung dieser Schnittstelle zu schreiben und zur Laufzeit in die gewünschte Implementierung einzufügen. "Abhängigkeitsinjektion" ist ein komplizierter Begriff für "Übergeben des richtigen Objekts als Argument".

In der Praxis arbeite ich derzeit an einem Problem mit maschinellem Lernen. Ich habe einen Algorithmus, der ein Vorhersagemodell erfordert. Aber ich möchte verschiedene Algorithmen für maschinelles Lernen ausprobieren. Also habe ich eine Schnittstelle definiert. Was brauche ich von meinem Vorhersagemodell? Bei gegebener Eingabestichprobe die Vorhersage und ihre Fehler:

interface Model {
  def predict(sample) -> (prediction: float, std: float);
}

Mein Algorithmus verwendet eine Factory-Funktion, die ein Modell trainiert:

def my_algorithm(..., train_model: (observations) -> Model, ...) {
  ...
  Model model = train_model(observations);
  ...
  y, std = model.predict(x)
  ...
}

Ich habe jetzt verschiedene Implementierungen der Modellschnittstelle und kann sie gegeneinander vergleichen. Eine dieser Implementierungen verwendet tatsächlich zwei andere Modelle und kombiniert sie zu einem verstärkten Modell. Also dank dieser Schnittstelle:

  • Mein Algorithmus muss nicht im Voraus über bestimmte Modelle Bescheid wissen.
  • Ich kann Modelle leicht austauschen, und
  • Ich habe viel Flexibilität bei der Implementierung meiner Modelle.

Ein klassischer Anwendungsfall für Polymorphismus ist die grafische Benutzeroberfläche. In einem GUI-Framework wie Java AWT / Swing /… gibt es verschiedene Komponenten . Die Komponentenschnittstelle / Basisklasse beschreibt Aktionen wie das Malen auf den Bildschirm oder das Reagieren auf Mausklicks. Viele Komponenten sind Container, die Unterkomponenten verwalten. Wie könnte sich ein solcher Container selbst zeichnen?

void paint(Graphics g) {
  super.paint(g);
  for (Component child : this.subComponents)
    child.paint(g);
}

Hier muss der Container nicht im Voraus die genauen Typen der Unterkomponenten kennen - solange sie mit der ComponentSchnittstelle übereinstimmen, kann der Container einfach die polymorphe paint()Methode aufrufen . Dies gibt mir die Freiheit, die AWT-Klassenhierarchie mit beliebigen neuen Komponenten zu erweitern.

Es gibt viele wiederkehrende Probleme in der Softwareentwicklung, die durch die Anwendung des Polymorphismus als Technik gelöst werden können. Diese wiederkehrenden Problem-Lösungs-Paare werden Entwurfsmuster genannt , und einige von ihnen sind im gleichnamigen Buch zusammengefasst. In diesem Buch wäre mein Modell des injizierten maschinellen Lernens eine Strategie , die ich verwende, um „eine Familie von Algorithmen zu definieren, jede einzelne zu kapseln und austauschbar zu machen“. Das Java-AWT-Beispiel, in dem eine Komponente Unterkomponenten enthalten kann, ist ein Beispiel für einen Verbund .

Es muss jedoch nicht für jedes Design Polymorphismus verwendet werden (über die Aktivierung der Abhängigkeitsinjektion für Komponententests hinaus, was ein wirklich guter Anwendungsfall ist). Die meisten Probleme sind ansonsten sehr statisch. Infolgedessen werden Klassen und Methoden häufig nicht für den Polymorphismus verwendet, sondern lediglich als bequeme Namespaces und für die hübsche Syntax von Methodenaufrufen. ZB bevorzugen viele Entwickler Methodenaufrufe account.getBalance()gegenüber weitgehend gleichwertigen Funktionsaufrufen Account_getBalance(account). Das ist ein perfekter Ansatz, es ist nur so, dass viele Methodenaufrufe nichts mit Polymorphismus zu tun haben.

amon
quelle
6

In den meisten UI-Toolkits sind viele Vererbungen und Polymorphismen zu sehen.

Beispielsweise Buttonerbt im JavaFX-UI-Toolkit, von ButtonBasewem Labelederbt, von Controlwem Regionerbt, von Parentwem erbt, von wem erbt, von wem Nodeerbt Object. Viele Ebenen überschreiben einige Methoden aus den vorherigen.

Wenn Sie möchten, dass diese Schaltfläche auf dem Bildschirm angezeigt wird, fügen Sie sie einer hinzu Pane, die alles akzeptiert, was Sie Nodeals Kind erben . Aber woher weiß ein Fenster, was mit einer Schaltfläche zu tun ist, wenn es nur als generisches Knotenobjekt betrachtet wird? Das Objekt könnte alles sein. Das Fenster kann dies tun, da der Button die Methoden von Node mit einer beliebigen knopfspezifischen Logik neu definiert. Der Bereich ruft nur die in Node definierten Methoden auf und überlässt den Rest dem Objekt selbst. Dies ist ein perfektes Beispiel für angewandten Polymorphismus.

UI-Toolkits haben eine sehr hohe reale Bedeutung, sodass sie sowohl aus akademischen als auch aus praktischen Gründen nützlich sind.

UI-Toolkits haben jedoch auch einen erheblichen Nachteil: Sie sind in der Regel sehr umfangreich . Wenn ein Neophyte-Softwareentwickler versucht, die internen Abläufe eines allgemeinen UI-Frameworks zu verstehen, trifft er häufig auf über hundert Klassen , von denen die meisten sehr esoterischen Zwecken dienen. „Was zum Teufel ist ein ReadOnlyJavaBeanLongPropertyBuilder? Ist es wichtig? Muß ich habe zu verstehen , was es ist gut?“ Anfänger können sich in diesem Kaninchenbau leicht verirren. So können sie entweder vor Schrecken fliehen oder an der Oberfläche bleiben, wo sie nur die Syntax lernen und versuchen, nicht zu genau darüber nachzudenken, was tatsächlich unter der Haube passiert.

Philipp
quelle
3

Obwohl es hier bereits schöne Beispiele gibt, besteht ein anderes darin, Tiere durch Geräte zu ersetzen:

  • Devicekann sein powerOn(), powerOff(), setSleep()und kann getSerialNumber().
  • SensorDeviceall dies tun können, und bieten polymorphe Funktionen wie getMeasuredDimension(), getMeasure(), alertAt(threashhold)und autoTest().
  • Natürlich getMeasure()wird nicht in der gleichen Weise für einen Temperatursensor, einen Lichtdetektor, einen Schalldetektor oder einen Volumensensor implementiert. Und natürlich kann jeder dieser spezialisierteren Sensoren einige zusätzliche Funktionen haben.
Christophe
quelle
2

Presentation ist eine sehr häufige Anwendung, die wahrscheinlich häufigste ist ToString (). Das ist im Grunde Animal.Speak (): Sie weisen ein Objekt an, sich zu manifestieren.

Allgemeiner gesagt sagt man einem Objekt, dass es "seine Sache machen" soll. Denken Sie an Save, Load, Initialize, Dispose, ProcessData, GetStatus.

Martin Maat
quelle
2

Meine erste praktische Anwendung des Polymorphismus war die Implementierung von Heap in Java.

Ich hatte eine Basisklasse mit der Implementierung von Methoden insert, removeTop, bei der der Unterschied zwischen max und min Heap nur darin besteht, wie Methodenvergleiche funktionieren.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Wenn ich also MaxHeap und MinHeap haben wollte, konnte ich einfach Vererbung verwenden.

class MaxHeap extends Heap {

   MaxHeap(int maxSize) {super(maxSize);}

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}
Bazil
quelle
1

Hier ist ein reales Szenario für den Polymorphismus von Webanwendungen / Datenbanktabellen :

Ich verwende Ruby on Rails, um Web-Apps zu entwickeln, und eine Sache, die viele meiner Projekte gemeinsam haben, ist die Möglichkeit, Dateien (Fotos, PDFs usw.) hochzuladen. So können beispielsweise Usermehrere Profilbilder in a und Productviele Produktbilder in a vorhanden sein. Beide haben das Verhalten, Bilder hochzuladen und zu speichern sowie die Größe zu ändern, Thumbnails zu generieren usw. Um trocken zu bleiben und das Verhalten für zu teilen Picture, möchten wir Picturepolymorph machen, so dass es zu beiden Userund gehören kann Product.

In Rails würde ich meine Modelle so gestalten:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

und eine Datenbankmigration zum Erstellen der picturesTabelle:

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

Die Spalten imageable_idund imageable_typewerden von Rails intern verwendet. Enthält im Grunde imageable_typeden Namen der Klasse ( "User", "Product"usw.) und imageable_idist die ID des zugeordneten Datensatzes. Also imageable_type = "User"und imageable_id = 1wäre der Rekord in der usersTabelle mit id = 1.

Auf diese Weise können wir beispielsweise auf user.picturesdie Bilder des Benutzers zugreifen und die Bilder product.pictureseines Produkts abrufen. Dann wird das gesamte bildbezogene Verhalten in der PhotoKlasse eingekapselt (und nicht für jedes Modell, das Fotos benötigt, eine separate Klasse), sodass die Dinge trocken bleiben.

Lesen Sie weiter: Polymorphe Assoziationen an Schienen .

Chris Cirefice
quelle
0

Es gibt viele Sortieralgorithmen wie Bubble-Sortierung, Insert-Sortierung, Quick-Sortierung, Heap-Sortierung usw. und sie haben unterschiedliche Komplexität. Welche Sortierung optimal ist, hängt von verschiedenen Faktoren ab (z. B. Größe des Arrays).

Der mit einer Sortierschnittstelle versehene Client kümmert sich nur darum, ein Array als Eingabe bereitzustellen und dann ein sortiertes Array zu empfangen. Während der Laufzeit kann abhängig von bestimmten Faktoren eine geeignete Sortierimplementierung verwendet werden. Dies ist ein Beispiel aus der Praxis, wo Polymorphismus verwendet wird.

Was ich oben beschrieben habe, ist ein Beispiel für Laufzeit-Polymorphismus, wohingegen das Überladen von Methoden ein Beispiel für das Kompilieren von Zeit-Polymorphsim ist, wobei die Kompilierbarkeit von I / P- und O / P-Parametertypen und der Anzahl der Parameter abhängt, dass der Aufrufer zur Kompilierbarkeit selbst die richtige Methode verwendet.

Hoffe das klärt sich.

rahulaga_dev
quelle