Eine kleine Programmieraufgabe…

By | 13.8.2017

…die sich als aufwändiger und lehrreicher herausgestellt hat, als ich dachte.

Es begann damit, dass ein Kollege mir von dem Computerspiel erzählte, das ihn gerade beschäftigte: Als Schiffbrüchiger auf einer Insel sammelt man unter anderem Gegenstände ein und erntet Pflanzen, jagt Tiere und arbeitet mit Werkzeugen. Zum Beispiel kann man mit einem Messer und einer Kokosnuss man Trinkwasser erzeugen (wobei die Kokosnuss verbraucht wird), und mit einem Schwein und einem Messer erzeugt man Haut und Fleisch, und aus der Haut kann man Leder machen, und aus dem Leder Riemen, und so weiter.

Ha, dachte ich mir, das ist nett, das sieht objektorientiert aus, das könnte ich doch auch mal mit Schülern machen. Erster Gedanke:

Klassendiagramm

Das Messer kriegt folgende Methoden:

  1. eine Methode anwendenAuf(Kokosnuss)
  2. eine Methode anwendenAuf(Schwein)
  3. eine Methode anwendenAuf(Spielobjekt)
    (eventuell verzichtbar, da ererbt, aber siehe weiter unten)

Diese drei Methoden heißen zwar alle gleich, sind aber trotzdem verschiedene Methoden und machen verschiedene Sachen – dieses Konzept heißt overloading. – Ein anderes Konzept ist das des overriding, wenn eine Unterklasse die gleichnamige Methode der Oberklasse überschreibt. Das ist der Fall, wenn Methode (3) die ererbte gleichnamige Methode überschreibt. Siehe weiter unten. – Wenn man die Methode anwendenAuf mit einem Kokosnuss-Objekt als Parameter aufruft, wird Methode (1) ausgewählt und ausgeführt, wenn man sie mit einem Schwein-Objekt aufruft, wird (2) ausgeführt, wenn man sie mit irgendeinem anderen Spielobjekt-Objekt als Parameter aufruft, wird die Methode (3) ausgeführt.

Dann würde jede Schülerin, jeder Schüler eine eigene Klasse erzeugen (Messer, Kokosnuss, Hammer, Feuerstein, Zange….) und würde für jede interessante Interaktion mit einem anderen Objekttyp eine eigene anwendenAuf-Methode schreiben – und die Catch-all-Methode für die restlichen Objekttypen übernehmen. Das fände ich ordentlich.

Das funktioniert auch wie gedacht, aber leider nur erst einmal, und dann doch nicht: In der Regel wird man eine Liste oder ein Array von Spielobjekten haben, und der Spieler wählt eins davon aus und wendet sein Messer darauf an. Und dann wird automatisch und stets die Methode (3) aufgerufen, auch wenn sich hinter dem Spielobjekt eine Kokosnuss verbirgt. Will heißen, wenn ich folgenden Code ausführe:

Spielobjekt o = new Kokosnuss();
Spielobjekt k = new Kokosnuss();
Spielobjekt m = new Messer();
m.anwendenAuf(o);
m.anwendenAuf(k);

Dann wird bei m.anwendenAuf(o) die Methode (3) aufgerufen, und nur bei m.anwendenAuf(k) wird die Methode (1). Denn wenn die ausführbaren Java-Klassen erzeugt werden, also beim Compilieren, also Übersetzen des Quellcodes, dann gilt: Die Kokosnuss o ist vom Typ Spielobjekt, basta. Das heißt static binding. Bei diesem Überstzen wird dann festgelegt, dass bei m.anwendenAuf(o) eben die Methode (3) ausgeführt wird. Und das geschieht dann auch, wenn das Programm tatsächlich ausgeführt wird.

Zwar hat beim Ausführen des Programms das Objekt o dann den Typ Kokosnuss, wie man herausfinden kann durch einen Aufruf von System.out.println( o.getClass() ). Aber das bringt nichts mehr, da ist die Wahl bereits getroffen. Etwas anders ist es bei ererbten und überschriebenen Methoden. Hier noch einmal die Zeilen von oben:

Spielobjekt o = new Kokosnuss();
Spielobjekt m = new Messer();
m.anwendenAuf(o);

In der letzten Zeile wird immer noch die Methode (3) anwendenAuf(Spielobjekt) aufgerufen. Aber es ist die Methode, die in der Klasse Messer definiert ist, und nicht die gleichnamige in der übergeordneten Klasse Spielobjekt. Das ist das overriding von vorhin, und dabei wird erst zur Laufzeit entschieden, also wenn das Programm tatsächlich läuft, welche der beiden Methoden aufgerufen wird. Die Maschine schaut zur Laufzeit, von welchem Typ m wirklich ist (nämlich Messer, und nicht Spielobjekt), und wählt dann die entsprechende Methode. Das heißt dann dynamic binding.

Kurz gesagt:

  1. Wenn ein Objekt eine ererbte Methode mit einer eigenen überschreibt, wird stets letztere ausgeführt. In der Unterklasse entscheidet man also selber, ob die ererbte Methode benutzt werden soll oder ob es stattdessen eine eigene Methode geben wird.
  2. Wenn ein Objekt allerdings als Parameter einer Methode übergeben wird, hat die Klasse, die diese Methode zur Verfügung stellt, keine Auswahlmöglichkeit und kann den Objekttyp des Parameters nicht als Auswahlkriterium heranziehen. Der wird vorher festgelegt auf das, was da steht, egal welche Klasse wirklich dahinter steckt.

Fazit: Die Klasse Messer braucht nur eine einzige Methode anwendenAuf(Spielobjekt). Man muss das ganze so machen:

void anwendenAuf(Spielobjekt o) {
  if (o instanceof Kokosnuss) {
    // ...
  }
  else if (o instanceof Schwein) {
    // ...
  }
  else {
    // ...
  }
}

Ich finde das ein bisschen weniger übersichtlich, als für jedes Objekt eine eigene Methode zu haben. Außerdem stört mich der instanceof-Operator. Mit dem kann man herausfinden, ob ein Objekt zu einer bestimmten Klasse (oder: Oberklasse davon) gehört. Er hat aber einen schlechten Ruf, weil er oft ein Anzeichen dafür ist, dass man nicht wirklich objektorientiert programmiert, weil sich seine Verwendung oft ersetzen lässt durch ordentliche Vererbung und überschriebene Methoden. Hier gibt es aber keine andere Lösung. Zugegeben: Man kann für Spielobjekt-Klassen ein eigenes Typ-Attribut einführen und es sich mit getTyp() geben lassen, und das als Entscheidungskriterium hernehmen. Dann hat jedes Kokosnussobjekt ein Attribut String typ = "kokosnuss", was mir aber auch nicht gefällt. Aber immerhin käme ich so um das Erklären von instanceof herum.

So oder so habe ich ein Problem. Nehmen wir an, ich habe Patronen, mit einem Attribut int anzahl, und Pistolen mit einem Attribut boolean geladen. Es könnte auch ein Zauberstab mit Ladungen sein, oder Goldmünzen:

class Patrone extends Spielobjekt {
 
  void anwendenAuf(Spielobjekt o) {
    if (o instanceof Pistole) {
      if (((Pistole) o).geladen == true) {
        // nichts, Pistole ist bereits geladen
      }
      else {
        ((Pistole) o).geladen = true; // Pistole ist jetzt geladen
        anzahl = anzahl-1;  // der Patronenhaufen wird um 1 reduziert
        if (anzahl == 0 ) { 
          entfernenAusInventar(this); // Patronenhaufen wird evtl. geloescht
        }
      }
    }
  }
 
}

Wenn ich in anwendenAuf auf das übergebene Objekt zugreifen möchte, muss ich einen Cast machen, also das Objekt manuell einer Unterklasse zuweisen. Das kann zu einem Fehler während des Ausführens führen, wenn das Objekt gar nicht zu dieser manuell zugewiesenen Klasse gehört. Wenn es gar nicht zu dieser manuell zugewiesenen Klasse gehören kann, wird bereits beim Compilieren/Übersetzen ein Fehler gemeldet. Dieses Casten ist für Schüler auch nicht leicht nachzuvollziehen, und auch eher schlechter Stil, wenn es denn anders geht. Aber: Es geht wohl nicht.

Später sieht das dann so aus:

Benutzeroberfläche Inselspiel
Wenn man zuerst auf das Schwert und dann auf eine Kuh klickt, verschwindet die Kuh aus dem Inventar und drei Rindfleisch tauchen auf.

Hmmm… wenn man eine Methode anwendenAuf (Spielobjekt []) einführt, kann man auch das Durchlaufen von Arrays üben. Mal sehen, wie ich das einbauen kann.

Fußnote:

Eine verlockende Sackgasse ist folgender Gedanke, mit dem der ursprüngliche Plan, verschiedene anwendenAuf-Methoden zu haben, doch noch verwirklicht werden könnte, wenn es den ginge.

Class c = o.getClass(); // wird zur Laufzeit bestimmt
c oNeu = (c) o; // müsste beim Compilieren überprüft werden können
anwendenAuf(c)

Man lässt sich von dem Spielobjekt o die Klasse geben, und castet dann o auf seine tatsächliche Klasse. Aber das geht nun einmal nicht, compiliert nicht einmal. Kurz: Ich kann nicht auf eine Klassentyp casten, der in einer Variablen gespeichert ist, weil dann bei der Compilierung nicht überprüft werden kann, ob das überhaupt ein grundsätzlich möglicher Cast wäre. Denn in einer Variablen kann ja alles stecken. Das weiß man erst zur Laufzeit.

Weitere Designfragen:

(1) Diese Regeln, was geschehen soll, wenn man das Messer auf die Kokosnuss anwendet – wo sollen die hin? Man kann es machen wie oben beschrieben, dann ist jede Klasse zuständig für sich selber. Wenn eine neue Klasse (etwa: Hühnchen) eingeführt werden soll, muss man in allen Klassen, deren Objekte etwas mit den Objekten der neuen Klasse anfangen können sollen, den Code ändern. Das ist umständlich. Stattdessen könnte man eine einzige Klasse haben, die alle möglichen Kombinationen von anwendenAuf(Spielobjekt o1, Spielobjekt o2) behandelt. Großer Vorteil: Führe ich neue Klassen ein, muss ich nur in einer Klasse Code ändern. Nachteil: Diese Methode wird ziemlich groß, da die Kombinationsmöglichkeiten mit der Anzahl der Klassen schnell steigen, auch wenn, zugegeben, nur die potentiell möglichen Kombinationen implmenetiert werden müssen. Bei 5 Typen gibt es maximal 25 Kombinationen, bei 10 Typen schon 100, das wächst geradezu… nein, nicht exponentiell, noch lange nicht, aber polynomiell/quadratisch. Und vor allem kann ich es dann nicht mehr so machen, dass jede Schülerin und jeder Schüler ihre eigenen Anwendungsregeln in eigenen Klassen erstellen.

(2) Richtig viel Arbeit kann ich mir machen, wenn ich Verb-Objekte einführe, also etwa eine Klasse Schneiden, die enthält, was geschieht, wenn ich ein Objekt des Interface-Typs „KannSchneiden“ auf ein Objekt des Interface-Typs „KannGeschnittenWerden“ anwende. So ähnlich ist das zwar bei Sprachen für Textadventures, aber so gründlich will ich gar nicht sein.

(3) Wenn ich vorher weiß, welche Unterklassen von Spielobjekt es geben soll, dann kann ich in die Spielobjekt-Oberklasse 30 leere, gegebenenfalls zu überschreibende Methoden platziere: anwendenAuf(Kokosnuss k) {}, anwendenAuf(Palme p) {} und so weiter. Dazu kommt dann eine zentrale Methode:

void anwendenAuf(Spielobjekt o) { 
  if (o instanceof Messer) anwendenAuf( (Kokosnuss) o); 
  else if (o instanceof Messer) anwendenAuf( (Palme) o); 
}

Diese Methode verteilt die Aufrufe dann auf die glechnamigen Methoden mit den korrekten Argumenten. Dann greift wieder das overriding und damit dynamic binding. Dann kann ich wieder meine ursprüngliche Idee umsetzen – separate Methoden für jeden Spielobjekttyp. Allerdings muss ich halt jedesmal den Code in dieser einen Klasse anpassen, wenn eine neue Unterklasse von Spielobjekt eingeführt wird.

(4) Keine Lösung habe ich bisher für das Folgende: Wenn ich ein Messer auf eine Kokosnuss anwende, geschieht dann das gleiche, wie wenn ich eine Kokosnuss auf ein Messer anwende? Wenn ich die Patronen anwende auf die Pistole, soll das gleiche passieren wie bei der Anwendung der Pistole auf die Patronen? Wenn nein, kein Problem, dann passt alles. Wenn ja… die Messerklasse entscheidet nur, wenn das Messer angewendet wird. In welcher Klasse soll stehen, was geschieht, wenn ich die Kokosnuss anwende? Wenn das in Kokosnuss ist, führt das zu Code-Dopplung, und wo sonst? Hier fällt mir nur ein, doch Lösung (1) zu wählen.

Links, die zeigen, dass viele Leute schon vor dem gleichen Problem standen:

(Und die Linksammlung erklärt, warum es nervig ist, wenn der Webfilter der Schule stackoverflow.com sperrt.)

11 thoughts on “Eine kleine Programmieraufgabe…

  1. Ingo

    Interessante Problematik und für mich ein Fall für die diversen Entwurfsmuster.

    Zunächst einmal muss das MVC Muster eingeführt werden: In der View hast du deine Oberfläche, die Objekte sind im Model und der Controller ist der Teil, der die Probleme macht. In der View würde dann Kuh und Messer angeklickt und diese Information an den Controller weitergereicht werden. Dieser entscheidet nun, was passieren muss.

    Mein erster Gedanke war: Das ist doch wie eine Grammatik-Regel! Ein bisschen googeln hat es bestätigt. Solche Regeln kann(!) man mit Zustandsautomaten (DEAs) umsetzen. Bin ich um Zustand Kuh und es kommt das Ereignis Messer, komme ich in den Zustand Steak. Bin ich im Zustand Messer und es kommt das Ereignis Kuh, komme ich ebenfalls in den Zustand Steak. Eine interessante Geschichte für die elfte Klasse (DEAs sind ja irgendwie gerichtete Graphen) und zwölfte Klasse (Implementierung von DEAs mit Hilfe des State-Design-Patterns)

    Ein anderes mögliches Entwurfsmuster wäre vielleicht das Command-Entwurfsmuster, aber da bin ich mir nicht so sicher.

    Alle Entwurfsmuster werden im übrigen großartig bei https://www.philipphauer.de/study/se/design-pattern.php an anschaulichen, gymnasialfreundlichen Beispielen mit Java erläutert.

  2. Herr Rau Post author

    Bonn: Ich fürchte, Claudia, dass ich das nicht schaffen werde, aber danke für den Hinweis. (Ich glaube, ich war noch nie in Bonn? Oder nur mal kurz. Hat sich nie ergeben.)

    Ingo: Mit MVC habe ich das auch mal gemacht. Der Controller ist ja erst mal ein ganz einfacher Automat mit zwei Zuständen: erstes Spielobjekt angeklickt/zweites Spielobjekt angeklickt. Wenn man weiter mit Automaten arbeitet, hat man dann ein State-Objekt für den Zustand „Messer“, in dem die Regeln für die Messer stehen. Das instanceof-Problem ist damit aber leider auch nicht gelöst, aber der Controller kann ja dann auf die korrekte Klasse casten und das Ergebnis weitergeben. (Alternativ: In der Spielobjekt-Oberklasse.) Schön einsetzbar ist wohl auch das Kompositum: Wenn ich in der GUI meine Objekte stapeln können will, also Fleisch+Fleisch gibt Fleisch (2) – das könnte man auch über ein Anzahl-Attribut regeln, aber vielleicht sollten die Objekte ja auch noch eigene Attributwerte haben und trotzdem stapelbar bleiben können. Also brauche ich ein Spielobjekt, einen Spielobjekthaufen, und ein gemeinsames Interface.

    In den Links wird immer wieder das Visitor-Pattern als Lösung für das instanceof-Problem empfohlen, aber das ist stets ein Missverstehen des Problems.

    Danke für den Link, habe mit Gewinn darin gelesen. Ein paar Muster kann ich inzwischen ganz gut, andere kann ich mir einfach nie merken.

  3. embee

    Du erwähnst den Namen des Problems wahrscheinlich bewusst nicht: Java hat kein multiple dispatch bzw keine sogenannten Multimethoden, d.h. nur der Typ des aufrufenden Objekts wird bei der Auswahl der aufzurufenden Methode berücksichtigt. Man kann das aber durch mehrere Stufen single dispatch ausdrücken: m.anwendenAuf(Spielobjekt o) ruft o.anwendenAuf (Messer m) auf, womit bejde Typen aufgelöst werden. Icj schreibe hier gerade vom Handy aus dem Urlaub und das ist extrem anstrengend (für einen Menschen über 30). Deswegen nicht ausführlicher. Bei Wikipedia gibt’s ein beispiel: https://en.m.wikipedia.org/wiki/Multiple_dispatch#Java
    Ich hab das früher schon so gemacht, aber ohne Rechner möchte ich doch nicht beschwören, dass man nicht doch noch ein paar Dinge beachten muss. Das Interface bei Wikipedia ist schön aber nicht zwingend notwendig, da reicht deine Oberklasse. Bin gespannt ob du damit etwas anfangen kannst.

  4. Herr Rau Post author

    Ja, so heißt das Problem; ich bin zwar auf den Namen gestoßen, kenne mich aber doch nicht gut genug uas, um ihn zu verwenden. Es ist leier auch keine Lösung dafür, wie ich das in der Schule einsetzen möchte: Denn die Messer-Funktion wird ja dann nicht zentral in der Messer-Klasse implementiert, sondern verteilt auf die anderen Klassen. Die Kokosnuss bestimmt, was geschieht, wenn ein Messer auf sie angewendet wird. Das ist vermutlich auch sinnvoll, aber wenn man eine neue Klasse einführt, etwa Zauberstab, dann muss man in alle anderen Klassen hinein, auf die er sich auswirkt.

    Dem möchte man vielleicht mit dem Command-Pattern entgegenwirken, aber dann müsste man darin wieder casten – oder, und dann braucht man es gar nicht, man führt Methoden für die Verben ein – schneiden(), verzaubern(), essen(), laden(KannGeladenWerden), die jeweils unterschiedlich implementiert werden.

  5. Andreas Weis

    Inwiefern geht der Visitor am Problem vorbei? Das sieht mir eher nach dem Lehrbuchbeispiel für genau dieses Pattern aus.

    Sie haben verschiedene Werkzeuge (Messer) und verschiedene Gegenstände auf die ein Werkzeug angewendet werden kann (Kokosnuss, Schwein). Eine Methode die ein Werkzeug auf einen Gegenstand anwendet, ruft zunächst Werkzeug.anwenden(Gegenstand) auf. Jedes Werkzeug bietet einen Override für anwenden() an (das ist das Äquivalent zur accept() Methode im Pattern). Damit wissen wir jetzt im Fall vom Messer, dass wir den Gegenstand schneiden wollen. Also ruft der Override (Messer.anwenden(Gegenstand)) nun Gegenstand.schneiden() auf. Das dispatcht nun wiederrum automatisch zur korrekten Methode (Kokosnuss.schneiden() oder Schwein.schneiden()).

    Wenn Sie die Implementierung lieber im Messer verstauen wollen, können Sie die Aufrufe auch einfach umdrehen. Wichtig ist, dass Sie pro Dispatch einen polymorphen Aufruf im Callstack haben. Wenn wir also mit Messer.anwendenAuf(Gegenstand) anfangen, brauchen wir auf jeden Fall noch einen zweiten polymorphen Aufruf auf Gegenstand, der uns den instanceof Call ersetzt. Das ist etwas knifflig, weil hier zwei Indirektionen im Spiel sind, von denen einer eigentlich nur boilerplate Code enthält, lässt sich aber immer so auflösen.

    Im Beispiel unterscheidet das Typsystem nicht zwischen Subjekt und Objekt der Interaktion (die einzige Basisklasse ist Spielobjekt). Damit lässt sich das Pattern auch umsetzen, wird dann aber potentiell unübersichtlich, da jedes Objekt auf jedes andere anwendbar sein muss, also damit beide Rollen im Visitor Pattern gleichzeitig implementieren muss, und entsprechend viel Boilerplate anfällt.

  6. embee

    Das ist aber auch ein bisschen Ansichtssache bzw. Design-Philosophie. Wenn ich, sagen wir, einen Kaktus neu einführe, der wie die Kokosnuss Wasser spenden kann, dann ist es schon eher die Eigenschaft des Kaktus, dass ich damit Wasser erzeugen kann, indem ich ihn mit Messer, Stein oder sonstwas bearbeite. Affordance nennen wir das in der Kognitionswissenschaft. Und dann würde ich das auch beim Kaktus und bei der Kokosnuss speichern. Aber klar, genauso ist es eine affordance des Messers, was ich alles damit machen kann. Letzten Endes zeigt sich da für mich, dass es bei diesem Spiel immer um die *Kombinationen* von Objekten geht, also die aktionsmöglichkeiten wahrscheinlich am besten doch separat gespeichert sollten. Command pattern klingt da schon richtig. Oder functor, ich kann mir die Nomenklatur da nie merken.

    Ich benutze für sowas immer anonyme innere Klassen, die das entsprechende Verhalten speichern. Vorteil: ich kann die definieren, wo ich will und z.B. In eine zentrale hashmap ablegen, so dass bei Gelegenheit die richtige rausgesucht werden kann.

    Das ist für den Unterricht aber bestimmt zu viel des Guten. Deshalb noch zum Schluss : ich glaube, dass es ziemlich einfach möglich sein müsste, dass von dir gewünschte Verhalten doch hinzubekommen, d.h. das Messer entscheidet. Man muss nur mit der Reihenfolge der Aufrufe experimentieren. Also du als Lehrer gibst die allgemeine spielobhekt Methode wirdBearbeitMit(spielobejkt tool) vor, die als kokos.wirdBearbeitetMit( messer) aufgerufen wird. In der Methode steht nichts anderes als tool.anwendenAuf(this) und das kann kann dann in den konkreten Klsssen überschrieben werden. Wichtig: wirdBearbeitetMit muss nur einmal in der allgemeinen aktionsbhandlung aufgerufen werden, die du wahrscheinlich ja sowieso vorgibst.

    Passt das vielleicht? (Wenn einer nachfragt warum das so gemacht wird, hast du natürlich einiges zu erklären…)

  7. Herr Rau Post author

    Vielen Dank, @Andreas. Ich habe mich jetzt eingehender mit dem Visitor beschäftigt, und der funktioniert sehr gut, selbst und besonders, wenn die Besucher- und besuchten Objekte zusammenfallen. Ich weiß noch nicht, ob ich Methoden wie schneiden() will, das wären die Verben, die ich müsste – wäre damit das angesprochen, was embee affordance nennt? Dann wäre das Ergebnis des Schneidens in Kokosnuss und Schwein verankert, die Möglichkeit des Schneidens in Messer und Säbel. Nachteil: ich muss vorher in den Interfaces festlegen, welche Verben grundsätzlich möglich sind.

    Warum ich das Visitor-Pattern zunächst abgelehnt habe: Anders als in meiner idealen Multiple-Dispatch-Welt mit dynamischer Bindung von Argumenten muss ich hier im Visitor festlegen, welche Elemente es gibt, die besucht werden können. Das wollte ich vermeiden, aber das geht nun mal nicht.

    Embee: Ja, das geht, und ist letztlich das, was herauskam, als ich das Visitor-Pattern auf das Minimum reduzierte. Wo ich die Kaktus- und die Messer-Funktionalität hintue, weiß ich auch nicht; mich interessiert das einmal, wie man das Problem im echten Einsatz lösen würde, und außerdem, wie ich das in der Schule einsetzen könnte. Ich probier das jetzt mal mit dem schneiden() und schaue, was herauskommt.

  8. Herr Rau Post author

    Mit den Methoden wetzen() und schneiden() bei jedem Gegenstand (der sowohl Visitor als auch Visitee sein kann) sieht das Ergebnis jetzt so aus:

    >Anwenden Messer auf: Kokosnuss
    >Kokosnuss wird geschnitten.
    >Anwenden Messer auf: Schwein
    >Schwein wird geschnitten.
    >Anwenden Messer auf: Messer
    >Messer wird gewetzt.

    Die Zeilen mit „Anwenden auf“ stammen dabei jeweils von dem Objekt der Messer-Klasse, die Zeilen darunter von Objekten der Klasse Kokosnuss, Schwein und Messer. Ich bin so zwar begrenzt auf (vorerst nur) wetzen und schneiden, aber das sind zusätzliche Methoden, um das Implementieren zu üben, und ist deshalb vielleicht sogar eine bessere Idee. Dann lege ich mit den SuS vorher fest, was es geben soll, und jeder entscheidet für seine Klasse auf welche andere Objekte es angewendet werden kann, und was passiert, wenn es gewetzt oder geschnitten oder gegessen wird.

  9. Andreas Weis

    Ja, der Visitor macht die ganze Geschichte furchtbar statisch und schwer zu erweitern. Was mit ein Grund ist, warum solcherlei Systeme heute (vor allem in der Spieleprogrammierung, wo sich Eigenschaften von Objekttypen oft im Laufe der Entwicklung drastisch ändern) nicht mit OOP, sondern mit so etwas wie Entity-Component-Systemen modelliert werden.

    Die sind leider in der Implementierung auch oft nicht einfacher nachzuvollziehen als der Visitor, bieten dafür aber sehr viel bessere Eigenschaften was Flexibilität und Performance angeht.

  10. Aginor

    Ich würde das spontan auch nicht so sehr objektorientiert machen. Wenn es nicht sehr viele Schneidwerkzeuge gibt ist der case-Verteiler in der Schneidmethode des Messers meines Erachtens eine gute Lösung.

    Einen Zustandsautomaten könnte man für das interagieren verwenden. ECS ist gut wenn man viele Objekte hat, vor allem wenn diese zur Laufzeit angepasst werden müssen.

    Gruß
    Aginor

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.