Modelliere die reale Welt oder die richtige OOP-Welt? Oder beides?

8

Ich schreibe ein Spiel, bei dem das mausgesteuerte Controller-Objekt auf ein Player-Objekt klickt, um etwas zu tun.

Es gibt zwei Möglichkeiten, die Interaktion zwischen Maus und Spieler zu initiieren:

  • Der Controller ruft die Funktion des Players auf:
    Der Controller überwacht Mausereignisse. Wenn ein Mausklick irgendwo auf dem Bildschirm auftritt, durchsucht der Controller alle Objekte unter dem Punkt, auf den geklickt wurde. Wenn eines der Objekte ein Spielerobjekt ist und die "anklickbare" Eigenschaft wahr ist, rufen Sie die entsprechende Funktion auf.
  • Player ruft Controller-Funktion auf:
    Der Spieler hört auf Mausereignisse. Wenn ein Mausklick auf den Player erfolgt und die "anklickbare" Eigenschaft des Players wahr ist, rufen Sie die entsprechende Funktion des Controllers auf.

Mein Dilemma besteht darin, dass die erste Option intuitiver erscheint, als ich mir das Szenario in der realen Welt vorstelle, aber die zweite Option scheint intuitiver zu sein, mit korrektem objektorientiertem Design, weil man nicht in die Eigenschaft eines anderen Objekts schauen muss , die die Kapselung zu einem gewissen Grad verletzt (der Controller muss in den Player schauen, um seine "anklickbare" Eigenschaft zu lesen). Außerdem scheint die zweite Option mit dem Entwurfsmuster 'Controller' in Einklang zu stehen.

Das ist immer ein Kampf für mich - trotze ich dem objektorientierten Design (z. B. Option 1) oder verwende ich eine Implementierung, die der realen Welt nicht zu entsprechen scheint (z. B. Option 2)?

Ich hoffe, es gibt eine Art Mittelweg, den ich vermisse.

    
Pup 30.08.2009, 17:03
quelle

11 Antworten

8
  

Das ist immer ein Kampf für mich - trotze ich dem objektorientierten Design (z. B. Option 1) oder verwende ich eine Implementierung, die der realen Welt nicht zu entsprechen scheint (z. B. Option 2)?

Ich glaube nicht, dass der Zweck der Objektorientierung darin besteht, die reale Welt zu modellieren.

Ich denke, dass ein (o) Grund, warum ein OO-Modell oft einem Modell der realen Welt folgt, dass sich die reale Welt nicht viel ändert: und deshalb die Wahl der realen Welt als Modell bedeutet, dass die Software wird sich nicht viel ändern, dh es wird kostengünstig zu pflegen sein.

Die Treue zur realen Welt ist kein Designziel für sich selbst: Stattdessen sollten Sie versuchen, Designs zu entwickeln, die andere Metriken maximieren, z. Einfachheit.

Die 'Objekte' in 'Objektorientierung' sind Software Objekte, nicht unbedingt reale Objekte.

    
ChrisW 30.08.2009, 18:05
quelle
5

Warum nicht mit Option 3 gehen, die Option 1 entspricht?

  • Der Controller ruft die Funktion des Players auf:
    Der Controller überwacht Mausereignisse. Wenn ein Mausklick irgendwo auf dem Bildschirm auftritt, durchsucht der Controller alle Objekte unter dem Punkt, auf den geklickt wurde. Wenn eines der Objekte IClickable implementiert oder clickable erbt (oder was auch immer), dann rufe seine entsprechende Funktion auf (oder feuere ein Ereignis, je nachdem, was angemessen ist).
strager 30.08.2009 17:29
quelle
4

Die zweite Methode ist die dezidiert idiomatische Flash-Art, Dinge zu übergeben. AS3 lässt das Ereignismodell direkt in EventDispatcher und alle DisplayObjects davon erben. Das bedeutet, dass jede Bitmap , Sprite oder MovieClip sofort weiß, ob auf sie geklickt wird.

Stellen Sie sich den Flash Player als Ihren Controller vor. Wenn ich MVC in Flash mache, schreibe ich fast nie einen Controller, weil der Flash Player das für Sie erledigt. Du verschwendest Zyklen, um zu bestimmen, worauf geklickt wurde, wenn der Flash Player es bereits weiß.

%Vor%

Ich würde wahrscheinlich nicht direkt von einem Sprite auf einen Controller zugreifen (wahrscheinlich in der Ansicht). Stattdessen würde ich ein Ereignis senden (wahrscheinlich ein bestimmtes benutzerdefiniertes Ereignis, das den Umständen entsprach). Treffen Sie Ihre Entscheidungen darüber, wie oft pro Frame etwas passiert. Das Reagieren auf eine Benutzerinteraktion (z. B. Mausklick) gibt Ihnen normalerweise die Freiheit, sich nicht um den Overhead in Ereignissystemen zu sorgen.

Schließlich - der Grund, warum ich das vorschlage, hat nichts mit irgendeinem Elfenbeinturm-Konzept von OOD oder OOP zu tun. Prinzipien wie diese existieren, um Ihnen zu helfen, Sie nicht einzuschränken. Wenn es um eine Frage der Pragmatik geht, gehen Sie mit der einfachsten Lösung, die Ihnen keine Kopfschmerzen auf der ganzen Linie verursacht. Manchmal bedeutet das, OOP zu machen, manchmal bedeutet es funktional, manchmal bedeutet es Imperativ.

    
James Fassett 30.08.2009 18:21
quelle
4

Das MVC-Konzept ist im Allgemeinen eine gute Übung und sollte immer noch als allgemeines Prinzip in Spieldesigns verwendet werden. Aber aufgrund der interaktiven Natur der Benutzeroberfläche des Spiels sollten Sie auch die ereignisbasierte Architektur betrachten. MVC widerspricht der Event-basierten Architektur nicht, wenn sie sorgfältig entworfen wird. (PureMVC als Beispiel.)

Ich empfehle Ihnen, beobachtbare Muster für alle Anzeigeobjekte zu verwenden, damit sie alle Ereignisse überwachen / auslösen können. Dies erspart Ihnen später viele Kopfschmerzen. Wenn Ihre Codebasis komplexer wird, werden Sie eventuell mehr Entkopplungstechniken verwenden müssen, als Ihre Option 2 beschreibt. Auch ein Mediator-Muster wird helfen.

Bearbeiten:

Mediator-Muster ist im Allgemeinen eine gute Möglichkeit, Ereignisse auf Anwendungsebene zu organisieren.

Hier ist ein Blog über MVC, Events und Mediatoren in der Spieleprogrammierung:

Ссылка

    
Aaron Qian 30.08.2009 17:17
quelle
2

Laut Anwenden von UML und Mustern (Craig Larman) sollte die Benutzeroberfläche (Ihre Mausereignisse) niemals interagieren mit Ihren Anwendungsklassen, dh die Benutzeroberfläche sollte niemals die Geschäftslogik direkt steuern.

Stattdessen sollten ein oder mehrere Controller als Mittelschicht für die Benutzeroberfläche definiert werden, sodass Option 1 tatsächlich einem guten objektorientierten Ansatz folgt.

Wenn Sie darüber nachdenken, macht es Sinn, so wenig Klassen wie möglich mit der Benutzeroberfläche zu koppeln, um die Geschäftslogik so unabhängig von der Benutzeroberfläche wie möglich zu machen.

    
Jorge Córdoba 09.09.2009 10:06
quelle
2
  

Das ist immer ein Kampf für mich - trotze ich dem objektorientierten Design (z. B. Option 1) oder verwende ich eine Implementierung, die der realen Welt nicht zu entsprechen scheint (z. B. Option 2)?

Die Realität mag ein guter Ausgangspunkt für das Formen oder Entwickeln eines Designs sein, aber es ist immer ein Fehler, ein OO-Design in die Realität umzusetzen.

OO Design ist über Schnittstellen, die Objekte, die sie implementieren, und die Interaktion zwischen diesen Objekten (die Nachrichten, die sie zwischen ihnen übergeben). Schnittstellen sind vertragliche Vereinbarungen zwischen zwei Komponenten, Modulen oder Software-Subsystemen. Es gibt viele Qualitäten für ein OO-Design, aber die wichtigste Eigenschaft für mich ist die Substitution. Wenn ich eine Schnittstelle habe, dann sollte der implementierende Code besser eingehalten werden. Aber noch wichtiger: Wenn die Implementierung getauscht wird, sollte die neue Implementierung besser eingehalten werden. Wenn schließlich die Implementierung polymorph sein soll, dann sollten die verschiedenen Strategien und Zustände der polymorphen Implementierung besser dazu passen.

Beispiel 1

In der Mathematik ist ein Quadrat ein Rechteck. Klingt nach einer guten Idee, die Klasse Square von der Klasse Rectangle zu erben. Du machst es und es führt zum Ruin. Warum? Weil die Erwartung oder die Überzeugung des Kunden verletzt wurde. Breite und Höhe können unabhängig voneinander variieren, aber Square verletzt diesen Vertrag. Ich hatte ein Rechteck der Dimension (10, 10) und ich setzte die Breite auf 20. Jetzt denke ich Ich habe ein Rechteck der Dimension (20, 10), aber die tatsächliche Instanz ist eine quadratische Instanz mit Dimensionen (20, 20) und ich, der Klient, bin eine echte Überraschung. Jetzt haben wir also eine Verletzung des Prinzips der geringsten Überraschung.

Jetzt haben Sie ein fehlerhaftes Verhalten, was dazu führt, dass der Client-Code komplex wird, als wären Anweisungen nötig, um das fehlerhafte Verhalten zu umgehen. Möglicherweise finden Sie auch Ihren Clientcode, der RTTI erfordert, um das fehlerhafte Verhalten zu umgehen, indem er nach Conrete-Typen sucht (ich habe einen Verweis auf Rectange, aber ich muss prüfen, ob es wirklich eine Square-Instanz ist).

Beispiel 2

Im wirklichen Leben können Tiere Fleischfresser oder Pflanzenfresser sein. Im wirklichen Leben sind Fleisch und Gemüse Lebensmittel. Sie könnten also denken, dass es eine gute Idee ist, die Klasse Animal als Elternklasse für verschiedene Tierarten zu haben. Sie denken auch, dass es eine gute Idee ist, eine FoodType-Elternklasse für Klasse Fleisch und Klasse Gemüse zu haben. Schließlich haben Sie Klasse Tier Sport eine Methode namens essen (), die einen FoodType als formelles Argument akzeptiert.

Alles wird kompiliert, statische Analyse und Links übergeben. Sie führen Ihr Programm aus. Was passiert zur Laufzeit, wenn ein Untertyp von Tier, etwa ein Pflanzenfresser, einen FoodType erhält, der eine Instanz der Meat-Klasse ist? Willkommen in der Welt der Covarience und Contravarience. Dies ist ein Problem für viele Programmiersprachen. Es ist auch ein interessantes und herausforderndes Problem für Sprachdesigner.

Fazit ...

Also, was machst du? Sie beginnen mit Ihrer Problemdomäne, Ihren User Stories, Ihren Anwendungsfällen und Ihren Anforderungen. Lass sie Design fahren. Lassen Sie sie Ihnen helfen, die Entitäten zu finden, die Sie in Klassen und Interfaces modellieren müssen. Wenn Sie das tun, werden Sie feststellen, dass das Endergebnis nicht auf der Realität basiert.

Sehen Sie sich Analysemuster von Martin Fowler an. Dort sehen Sie, was seine objektorientierten Designs antreibt. Es basiert hauptsächlich darauf, wie seine Kunden (Mediziner, Finanzleute, etc.) ihre täglichen Aufgaben erledigen. Es hat sich mit der Realität überschnitten, aber es basiert nicht auf der Realität oder ist von ihr getrieben.

    
Nicholas Salerno 16.09.2009 21:45
quelle
1

Dies kommt oft zur Präferenz. Logisches Spiele-Design steht sehr häufig im Widerspruch zu einem guten OOP-Design. Ich lehne mich an das, was im Rahmen des Spieluniversums sinnvoll ist, aber es gibt absolut keine richtige Antwort auf die Frage und jedes einzelne Problem sollte für sich genommen betrachtet werden.

Dies ist etwas ähnlich zu streiten über die Vor- und Nachteile von Camel Casing.

    
Rushyo 30.08.2009 17:17
quelle
1

Ich denke, es ist wichtig, die Eingangslogik von der Anwendungslogik zu trennen ... Ein Ansatz besteht darin, Eingangsereignisse (sei es Benutzereingaben oder einige Daten, die über Sockets / lokale Verbindungen ankommen) in Anwendungsereignisse (Ereignisse in einer abstrakte Bedeutung des Wortes) ... diese Umwandlung wird von etwas getan, was ich "Frontcontroller" nennen würde, weil es keinen besseren Begriff gibt ...

alle diese Frontsteuerungen konvertieren einfach Ereignisse und sind somit völlig unabhängig davon, wie die Anwendungslogik auf bestimmte Ereignisse reagiert ... die Anwendungslogik hingegen ist von diesen Frontcontrollern entkoppelt ... das "Protokoll" ist ein vordefinierter Satz von Ereignissen ... Wenn es um den Benachrichtigungsmechanismus geht, liegt es an Ihnen, ob Sie die AS3-Ereignisverteilung verwenden, um Ereignisse von Frontcontrollern zu Anwendungscontrollern abzurufen, oder ob Sie sie für eine bestimmte Schnittstelle erstellen rufe an ...

Menschen neigen dazu, Anwendungslogik in Click-Handler für Buttons zu schreiben ... und manchmal sogar, diese Handler manuell auszulösen, weil sie nichts aufräumen wollen ... nichts, was ich noch nicht gesehen habe ...

Ja, es ist definitiv Version 1 ... in diesem Fall muss der Frontcontroller für die Mauseingaben nur die Displayliste kennen und logisch darüber sein, wann das Ereignis gesendet werden soll ... und der Applikator muss sein kann in diesem Fall irgendeine Art von PlayerEvent.SELECT -Ereignis verarbeiten ... (wenn du dich später dafür entscheidest, irgendeine Art von Tutorial-Modus zu haben, kannst du einfach die falsche Maus bewegen und dieses Ereignis im Falle einer Fälschung versenden klickt, oder Sie können einfach alles in einer Art Replay-Sache wiederholen, oder Sie können dies für die Aufnahme von Makros verwenden, wenn es nicht um Spiele geht ... nur um einige Szenarien aufzuzeigen, in denen diese Trennung hilfreich ist)

hoffe das hilft ...;)

    
back2dos 30.08.2009 18:59
quelle
1

Ich würde Ihrer Einschätzung der beiden Optionen nicht zustimmen können. Option 2 ist schlechter OO, da es das Player-Objekt eng an die spezifische Benutzeroberflächenimplementierung koppelt. Was passiert, wenn Sie Ihre Player-Klasse an einem anderen Ort verwenden möchten, an dem sie nicht angezeigt wird oder eine mauslose Benutzeroberfläche verwendet wird?

Option 1 kann jedoch noch verbessert werden. Der frühere Vorschlag, eine iClickable-Schnittstelle oder eine anklickbare Oberklasse zu verwenden, ist eine immense Verbesserung, da Sie damit mehrere Arten anklickbarer Objekte (nicht nur Player) implementieren können, ohne dem Controller eine riesige Liste von "Ist das Objekt diese Klasse?" es diese Klasse? " durchmachen.

Ihr Haupteinwand gegen Option 1 scheint zu sein, dass es die "anklickbare" Eigenschaft des Players überprüft, die Ihrer Meinung nach die Kapselung verletzt. Es tut es nicht. Es überprüft eine Eigenschaft, die als Teil der öffentlichen Schnittstelle des Players definiert ist. Dies ist nicht anders als den Aufruf einer Methode über die öffentliche Schnittstelle.

Ja, ich weiß, dass in diesem Moment die "clickable" -Eigenschaft so implementiert wird, dass es ein einfacher Getter ist, der nichts anderes tut, als den internen Status des Players abzufragen, aber nicht sein . Die Eigenschaft könnte morgen neu definiert werden, um ihren Rückgabewert auf eine völlig andere Weise zu bestimmen, ohne auf den internen Zustand Bezug zu nehmen, und solange sie einen booleschen Wert zurückgibt (dh die öffentliche Schnittstelle bleibt gleich), wird Code mit Player.clickable verwendet funktionieren immer noch gut. Das ist der Unterschied zwischen einer Eigenschaft und einer direkten Inspektion des inneren Zustands - und das ist ein gewaltiger großer Unterschied.

Wenn Sie sich trotzdem unwohl fühlen, ist es einfach genug, den Controller nicht Player.clickable überprüfen zu lassen: Senden Sie einfach das Klick-Ereignis an jedes Objekt unter der Maus, das iClickable / absteigende von Clickable implementiert. Wenn das Objekt in einem nicht klickbaren Zustand ist, wenn es den Klick erhält, kann es es einfach ignorieren.

    
Dave Sherohman 09.09.2009 10:27
quelle
1

In jeder OOP-Sprache ist es fast immer das Richtige, dem idiomatischen Ansatz zu folgen, wenn es auch die Möglichkeit gibt, dem Real-Life-Emulation-Ansatz zu folgen. Um Ihre Frage zu beantworten, ist der zweite Ansatz mit ziemlicher Sicherheit der bessere oder besser, je nachdem, wie Sie tiefer in das Design eintauchen oder später das Bedürfnis haben, es zu ändern oder zu ergänzen.

Dies sollte Sie nicht davon abhalten, andere Lösungen zu finden. Aber versuche immer bei der Sprache zu bleiben. OOP übersetzt nicht in das reale Leben in einer 1: 1-Beziehung, noch ist es besonders gut darin, es nachzuahmen. Ein anschauliches Beispiel ist die klassische Objektbeziehung Rechteck und Quadrat, von der Sie wahrscheinlich schon alles wissen. Im wirklichen Leben ist ein Quadrat ein Rechteck. In der OOP (oder zumindest in der richtigen OOP) lässt sich diese Beziehung nicht gut in eine Basis-abgeleitete Beziehung übersetzen. Sie haben also das Bedürfnis, sich von der echten Emulation zu lösen, weil das Sprachidiom höher spricht. Es ist entweder das oder eine Welt des Schmerzes, wenn Sie anfangen, Rechteck und Quadrat ernsthaft zu implementieren, oder später Änderungen an ihnen vornehmen möchten.

    
Alexandre Bell 11.09.2009 03:14
quelle
1

Ein anderer Ansatz wäre das Erstellen einer IClickHandler-Schnittstelle. Alle Objekte, die sich für Klicks registrieren, tun dies, indem sie einen IClickHandler an den Controller übergeben. Wenn auf ein Objekt geklickt wird, ruft der Controller die clicked () -Methode für den registrierten IClickHandler auf. Der IClickHandler kann den Anruf an die bei ihm registrierte Methode weiterleiten oder nicht weiterleiten. Jetzt trifft weder Ihr Controller noch Ihr Objekt die Entscheidung darüber, ob das Objekt tatsächlich angeklickt wurde oder nicht.

Der IClickHandler könnte auch basierend auf anderen Kriterien ausgewählt werden (IOW das Objekt wählt nicht den IClickHandler selbst bei der Registrierung, ein anderer Algorithmus wählt ihn). Ein gutes Beispiel wäre, dass alle NPC-Objekte einen IClickHandler erhalten, der die Klicks weiterleitet, während alle Bäume einen IClickHandler erhalten, der die Klicks nicht weiterleitet.

Sie können mindestens 3 Handler haben, die das Interface implementieren: AlwaysClickable, NeverClickable, ToggledClickable

Denken Sie daran, dass das obengenannte mehr bewegliche Teile und einen kleinen Performance-Hit beinhaltet, aber es gibt Ihnen viel Flexibilität (es liegt an Ihnen, zu entscheiden, ob die Flexibilität die zusätzliche Komplexität wert ist).

Beachten Sie auch, dass es am besten ist, sich nicht an Prinzipien irgendeiner Art zu halten. Machen Sie das Beste, wenn Sie den Code schreiben. Wenn Sie einen Tetris-Klon schreiben, ist die Tatsache, dass Option 1 gegen die OOP-Prinzipien verstößt, völlig irrelevant. Sie werden nie die Vorteile einer strengen OOP für ein so einfaches Projekt erkennen.

    
Fred 16.09.2009 22:28
quelle