it-swarm.com.de

Null-Verhaltens-Objekte in OOP - mein Design-Dilemma

Die Grundidee hinter OOP ist, dass Daten und Verhalten (auf diesen Daten) untrennbar miteinander verbunden sind und durch die Idee eines Objekts einer Klasse gekoppelt sind. Objekte haben Daten und Methoden, die damit arbeiten ( und andere Daten). Offensichtlich werden nach den Prinzipien von OOP Objekte, die nur Daten sind (wie C-Strukturen), als Anti-Muster betrachtet.

So weit, ist es gut.

Das Problem ist, dass ich bemerkt habe, dass mein Code in letzter Zeit immer mehr in Richtung dieses Anti-Musters geht. Mir scheint, je mehr ich versuche, Informationen zu erreichen, die sich zwischen Klassen und lose gekoppelten Designs verstecken, desto mehr werden meine Klassen zu einer Mischung aus reinen Daten ohne Verhaltensklassen und allen Verhaltensklassen ohne Daten.

Ich entwerfe Klassen im Allgemeinen so, dass das Bewusstsein für die Existenz anderer Klassen minimiert und das Wissen über die Schnittstellen anderer Klassen minimiert wird. Ich setze dies besonders von oben nach unten durch. Klassen niedrigerer Ebenen wissen nichts über Klassen höherer Ebenen. Z.B.:

Angenommen, Sie haben eine allgemeine Kartenspiel-API. Sie haben eine Klasse Card. Jetzt muss diese Card Klasse die Sichtbarkeit für die Spieler bestimmen.

Eine Möglichkeit besteht darin, boolean isVisible(Player p) in der Klasse Card zu haben.

Eine andere Möglichkeit besteht darin, boolean isVisible(Card c) in der Klasse Player zu haben.

Ich mag den ersten Ansatz besonders nicht, da er Wissen über eine höhere Klasse Player einer Klasse niedrigerer Ebene Card verleiht.

Stattdessen habe ich mich für die dritte Option entschieden, bei der wir eine Viewport -Klasse haben, die bei einem Player und einer Liste von Karten bestimmt, welche Karten sichtbar sind.

Dieser Ansatz beraubt jedoch sowohl die Klassen Card als auch Player einer möglichen Elementfunktion. Sobald Sie dies für andere Dinge als die Sichtbarkeit von Karten tun, bleiben Ihnen die Klassen Card und Player übrig, die reine Daten enthalten, da alle Funktionen in anderen Klassen implementiert sind, bei denen es sich meist um Klassen ohne Daten handelt , nur Methoden, wie die Viewport oben.

Dies ist eindeutig gegen die Grundidee von OOP.

Welches ist der richtige Weg? Wie soll ich vorgehen, um Klassenabhängigkeiten zu minimieren und angenommenes Wissen und Kopplung zu minimieren, ohne jedoch mit einem seltsamen Design zu enden, bei dem alle Klassen auf niedriger Ebene nur Daten enthalten und Klassen auf hoher Ebene alle Methoden enthalten? Hat jemand eine dritte Lösung oder Perspektive für das Klassendesign, die das ganze Problem vermeidet?

P.S. Hier ist ein weiteres Beispiel:

Angenommen, Sie haben die Klasse DocumentId, die unveränderlich ist und nur ein einziges BigDecimal id - Mitglied und einen Getter für dieses Mitglied hat. Jetzt müssen Sie irgendwo eine Methode haben, die mit einem DocumentIdDocument für diese ID aus einer Datenbank zurückgibt.

Machst du:

  • Fügen Sie der Klasse DocumentId die Methode Document getDocument(SqlSession) hinzu und führen Sie plötzlich Kenntnisse über Ihre Persistenz ("we're using a database and this query is used to retrieve document by id"), Die API für den Zugriff auf DB und dergleichen ein. Auch diese Klasse benötigt jetzt eine Persistenz-JAR-Datei, nur um sie zu kompilieren.
  • Fügen Sie eine andere Klasse mit der Methode Document getDocument(DocumentId id) hinzu, und lassen Sie die Klasse DocumentId als tot, ohne Verhalten, strukturähnliche Klasse.
96
RokL

Was Sie beschreiben, ist als anämisches Domänenmodell bekannt. Wie bei vielen OOP Designprinzipien (wie dem Gesetz von Demeter usw.) lohnt es sich nicht, sich nach hinten zu beugen, nur um eine Regel zu erfüllen.

Es ist nichts Falsches daran, Taschen voller Werte zu haben, solange sie nicht die gesamte Landschaft überladen und sich nicht auf andere Objekte verlassen, um die Hausarbeit zu erledigen, die sie für sich selbst erledigen könnten .

Es wäre sicherlich ein Codegeruch, wenn Sie eine separate Klasse hätten, nur um die Eigenschaften von Card zu ändern - wenn vernünftigerweise erwartet werden könnte, dass sie sich selbst darum kümmern.

Aber ist es wirklich eine Aufgabe eines Card zu wissen, für welches Player es sichtbar ist?

Und warum Card.isVisibleTo(Player p) implementieren, aber nicht Player.isVisibleTo(Card c)? Oder umgekehrt?

Ja, Sie können versuchen, eine Regel dafür zu finden, wie Sie es getan haben - wie Player höher als ein Card (?) -, aber es ist nicht so einfach zu erraten und ich muss an mehr als einer Stelle suchen, um die Methode zu finden.

Im Laufe der Zeit kann es zu einem schlechten Designkompromiss bei der Implementierung von isVisibleTo auf sowohl Card als auch Player kommen ] Klasse, die ich für ein Nein-Nein halte. Warum so? Da ich mir bereits den beschämenden Tag vorstelle, an dem player1.isVisibleTo(card1) einen anderen Wert zurückgibt als card1.isVisibleTo(player1). Ich denke - es ist subjektiv - sollte dies durch Design unmöglich gemacht werden .

Die gegenseitige Sichtbarkeit von Karten und Spielern sollte besser durch eine Art Kontextobjekt geregelt werden - sei es Viewport, Deal oder Game.

Es ist nicht gleichbedeutend mit globalen Funktionen. Schließlich kann es viele gleichzeitige Spiele geben. Beachten Sie, dass dieselbe Karte an vielen Tischen gleichzeitig verwendet werden kann. Sollen wir für jedes Pik-Ass viele Card -Instanzen erstellen?

Ich implementiere möglicherweise immer noch isVisibleTo auf Card, übergebe ihm jedoch ein Kontextobjekt und lasse Card die Abfrage delegieren. Programm zur Schnittstelle, um eine hohe Kopplung zu vermeiden.

Was Ihr zweites Beispiel betrifft: Wenn die Dokument-ID nur aus einem BigDecimal besteht, warum überhaupt eine Wrapper-Klasse dafür erstellen?

Ich würde sagen, alles was Sie brauchen ist ein DocumentRepository.getDocument(BigDecimal documentID);

Übrigens, während es in Java nicht vorhanden ist, gibt es in C # struct.

Sehen

als Referenz. Es ist eine sehr objektorientierte Sprache, aber niemand macht eine große Sache daraus.

42
Konrad Morawski

Die Grundidee hinter OOP) ist, dass Daten und Verhalten (auf diesen Daten) untrennbar miteinander verbunden sind und durch die Idee eines Objekts einer Klasse gekoppelt sind.

Sie machen den allgemeinen Fehler anzunehmen, dass Klassen ein grundlegendes Konzept in OOP sind. Klassen sind nur ein besonders beliebter Weg, um eine Kapselung zu erreichen. Aber wir können das zulassen.

Angenommen, Sie haben eine allgemeine Kartenspiel-API. Sie haben eine Klassenkarte. Jetzt muss diese Kartenklasse die Sichtbarkeit für die Spieler bestimmen.

GUTE HIMMEL NR. Wenn du Bridge spielst, frag die Sieben der Herzen wann ist es Zeit, die Hand des Dummys von einem Geheimnis, das nur dem Dummy bekannt ist, in ein Geheimnis zu ändern, das allen bekannt ist? Natürlich nicht. Das ist überhaupt kein Problem der Karte.

Eine Möglichkeit besteht darin, den booleschen Wert isVisible (Player p) in der Kartenklasse zu haben. Eine andere Möglichkeit besteht darin, einen booleschen Wert isVisible (Karte c) in der Spielerklasse zu haben.

Beide sind schrecklich; Mach keines davon. Weder der Spieler noch die Karte sind für die Umsetzung der Bridge-Regeln verantwortlich !

Stattdessen habe ich mich für die dritte Option entschieden, bei der wir eine Viewport-Klasse haben, die anhand eines Spielers und einer Liste von Karten bestimmt, welche Karten sichtbar sind.

Ich habe noch nie zuvor Karten mit einem "Ansichtsfenster" gespielt, daher habe ich keine Ahnung, was diese Klasse einschließen soll. Ich habe Karten mit ein paar Kartenspielen, einigen Spielern, einem Tisch und einer Kopie von Hoyle gespielt. Welches dieser Dinge repräsentiert Viewport?

Dieser Ansatz beraubt jedoch sowohl die Karten- als auch die Spielerklasse einer möglichen Mitgliedsfunktion.

Gut!

Sobald Sie dies für andere Dinge als die Sichtbarkeit von Karten tun, bleiben Ihnen Karten- und Player-Klassen übrig, die reine Daten enthalten, da alle Funktionen in anderen Klassen implementiert sind, bei denen es sich meistens um Klassen ohne Daten handelt, sondern nur um Methoden wie das obige Ansichtsfenster. Dies ist eindeutig gegen die Grundidee von OOP.

Nein; Die Grundidee von OOP ist, dass Objekte ihre Bedenken zusammenfassen . In Ihrem System ist eine Karte nicht sehr besorgt Ein Spieler ist es auch nicht. Dies liegt daran, dass Sie die Welt genau modellieren. In der realen Welt sind die Eigenschaften Karten, die für ein Spiel relevant sind, äußerst einfach. Wir könnten die Bilder auf den Karten ersetzen mit den Zahlen von 1 bis 52, ohne das Spiel zu verändern. Wir könnten die vier Personen durch Mannequins mit den Bezeichnungen Nord, Süd, Ost und West ersetzen, ohne das Spiel wesentlich zu verändern. Spieler und Karten sind die einfachste Dinge in der Welt der Kartenspiele. Die Regeln sind kompliziert, daher sollte in der Klasse, die die Regeln darstellt, die Komplikation liegen.

Wenn einer Ihrer Spieler eine KI ist, kann sein interner Zustand äußerst kompliziert sein. Diese KI bestimmt jedoch nicht, ob sie eine Karte sehen kann. Die Regeln bestimmen das.

So würde ich Ihr System entwerfen.

Zunächst einmal sind Karten überraschend kompliziert, wenn es Spiele mit mehr als einem Deck gibt. Sie müssen sich die Frage stellen: Können Spieler zwischen zwei Karten des gleichen Ranges unterscheiden? Wenn Spieler eins eines der sieben Herzen spielt und dann etwas passiert und Spieler zwei eines der sieben Herzen spielt, kann Spieler drei feststellen, dass es dasselbe war sieben Herzen? Überlegen Sie dies sorgfältig. Abgesehen von dieser Sorge sollten Karten sehr einfach sein. Sie sind nur Daten.

Was ist die Natur eines Spielers? Ein Spieler verbraucht eine Folge von sichtbaren Aktionen und erzeugt eine Aktion .

Das Regelobjekt koordiniert all dies. Die Regeln erzeugen eine Folge von sichtbaren Aktionen und informieren die Spieler:

  • Spieler eins, die Zehn der Herzen wurde dir von Spieler drei übergeben.
  • Spieler zwei, Spieler drei hat Spieler drei eine Karte ausgehändigt.

Und bittet dann den Spieler um eine Aktion.

  • Spieler eins, was willst du machen?
  • Spieler eins sagt: Verdreifache das fromp.
  • Spieler eins, das ist eine illegale Aktion, weil ein verdreifachtes Fromp ein nicht zu rechtfertigendes Spiel erzeugt.
  • Spieler eins, was willst du machen?
  • Spieler eins sagt: Wirf die Pik-Dame ab.
  • Spieler zwei, Spieler eins hat die Pik-Dame abgeworfen.

Und so weiter.

Trennen Sie Ihre Mechanismen von Ihren Richtlinien. Die Richtlinien des Spiels sollten gekapselt sein in einem Richtlinienobjekt, nicht in den Karten. Die Karten sind nur ein Mechanismus.

150
Eric Lippert

Sie haben Recht, dass die Kopplung von Daten und Verhalten die zentrale Idee von OOP ist, aber es steckt noch mehr dahinter. Beispiel: Kapselung: OOP/modulare Programmierung ermöglicht es uns, eine öffentliche Schnittstelle von Implementierungsdetails zu trennen. In OOP bedeutet dies, dass die Daten niemals öffentlich zugänglich sein sollten und nur über Accessoren verwendet werden sollten. Nach dieser Definition ist ein Objekt ohne Methoden tatsächlich nutzlos.

Eine Klasse, die keine Methoden jenseits von Accessoren bietet, ist im Wesentlichen eine überkomplizierte Struktur. Dies ist jedoch nicht schlecht, da OOP Ihnen die Flexibilität bietet, interne Details zu ändern, was eine Struktur nicht tut. Anstatt beispielsweise einen Wert in einem Mitgliedsfeld zu speichern, könnte er neu berechnet werden Jedes Mal wird ein Backing-Algorithmus geändert und damit der Status, der verfolgt werden muss.

Während OOP einige klare Vorteile hat (insbesondere gegenüber einfacher prozeduraler Programmierung), ist es naiv, nach „reinem“ OOP zu streben. Einige Probleme lassen sich nicht gut auf einen objektorientierten Ansatz abbilden und werden gelöst leichter durch andere Paradigmen. Wenn Sie auf ein solches Problem stoßen, bestehen Sie nicht auf einem minderwertigen Ansatz.

  • Erwägen Sie die Berechnung der Fibonacci-Sequenz objektorientiert. Ich kann mir keinen vernünftigen Weg vorstellen, das zu tun. Eine einfache strukturierte Programmierung bietet die beste Lösung für dieses Problem.

  • Ihre isVisible Beziehung gehört zu beiden Klassen oder zu keiner oder tatsächlich: zum Kontext. Verhaltenslose Aufzeichnungen sind typisch für einen funktionalen oder prozeduralen Programmieransatz, der am besten zu Ihrem Problem passt. Daran ist nichts auszusetzen

    static boolean isVisible(Card c, Player p);
    

    und es ist nichts Falsches daran, dass Card keine Methoden außer rank und suit Accessoren hat.

29
amon

Die Grundidee hinter OOP ist, dass Daten und Verhalten (auf diesen Daten) untrennbar miteinander verbunden sind und durch die Idee eines Objekts einer Klasse gekoppelt sind. Objekte haben Daten und Methoden, die damit arbeiten ( und andere Daten). Offensichtlich werden nach den Prinzipien von OOP Objekte, die nur Daten sind (wie C-Strukturen), als Anti-Muster betrachtet. (...) Dies ist eindeutig gegen die Grundidee von OOP.

Dies ist eine schwierige Frage, da sie auf einigen fehlerhaften Prämissen basiert:

  1. Die Idee, dass OOP] die einzig gültige Möglichkeit ist, Code zu schreiben.
  2. Die Idee, dass OOP ein klar definiertes Konzept ist. Es ist zu einem solchen Schlagwort geworden, dass es schwierig ist, zwei Personen zu finden, die sich darüber einig sind, worum es bei OOP) geht.
  3. Die Idee, dass OOP] Daten und Verhalten bündelt.
  4. Die Idee, dass alles eine Abstraktion ist/sein sollte.

Ich werde nicht viel auf # 1-3 eingehen, da jeder seine eigene Antwort hervorbringen könnte und dies zu vielen meinungsbasierten Diskussionen einlädt. Besonders beunruhigend finde ich die Idee von "OOP geht es um die Kopplung von Daten und Verhalten". Dies führt nicht nur zu # 4, sondern auch zu der Idee, dass alles eine Methode sein sollte.

Es gibt einen Unterschied zwischen den Operationen, die einen Typ definieren, und der Art und Weise, wie Sie diesen Typ verwenden können. Das Abrufen des i -ten Elements ist für das Konzept eines Arrays von wesentlicher Bedeutung, aber das Sortieren ist nur eines der vielen Dinge, die ich mit einem Element auswählen kann. Das Sortieren muss nicht mehr eine Methode sein als "ein neues Array erstellen, das nur die geraden Elemente enthält".

Bei OOP geht es um die Verwendung von Objekten. Objekte sind nur ein Weg, um Abstraktion zu erreichen . Abstraktion ist ein Mittel, um unnötige Kopplungen in Ihrem Code zu vermeiden, und kein Selbstzweck. Wenn Ihre Vorstellung von einer Karte ausschließlich durch den Wert ihrer Suite und ihres Ranges definiert wird, ist es in Ordnung, sie als einfaches Tupel oder Datensatz zu implementieren. Es gibt keine unwesentlichen Details, von denen ein anderer Teil des Codes eine Abhängigkeit bilden könnte. Manchmal hat man einfach nichts zu verbergen.

Sie würden isVisible nicht zu einer Methode vom Typ Card machen, da es für Ihre Vorstellung von einer Karte wahrscheinlich nicht wesentlich ist, sichtbar zu sein (es sei denn, Sie haben ganz spezielle Karten, die durchscheinend oder undurchsichtig werden können. .). Sollte es eine Methode vom Typ Player sein? Nun, das ist wahrscheinlich auch keine definierende Qualität der Spieler. Sollte es Teil eines Viewport Typs sein? Dies hängt wiederum davon ab, wie Sie ein Ansichtsfenster definieren und ob der Gedanke, die Sichtbarkeit von Karten zu überprüfen, ein wesentlicher Bestandteil der Definition eines Ansichtsfensters ist.

Es ist sehr gut möglich, dass isVisible nur eine freie Funktion sein sollte.

19
Doval

Nach den Prinzipien von OOP werden Objekte, die nur Daten sind (wie C-Strukturen), offensichtlich als Anti-Muster betrachtet.

Nein, das sind sie nicht. Plain-Old-Data-Objekte sind ein absolut gültiges Muster, und ich würde sie in jedem Programm erwarten, das sich mit Daten befasst, die zwischen verschiedenen Bereichen Ihres Programms beibehalten oder kommuniziert werden müssen.

Während Ihre Datenschicht möglicherweise beim Einlesen aus der Tabelle Player eine vollständige Players -Klasse aufspult, könnte es sich stattdessen nur um eine allgemeine Datenbibliothek handeln, die a zurückgibt POD mit den Feldern aus der Tabelle, die an einen anderen Bereich Ihres Programms übergeben werden, der einen Player-POD in Ihre konkrete Klasse Player konvertiert.

Die Verwendung von typisierten oder untypisierten Datenobjekten ist in Ihrem Programm möglicherweise nicht sinnvoll, macht sie jedoch nicht zu einem Anti-Pattern. Wenn sie Sinn machen, verwenden Sie sie und wenn nicht, nicht.

9
DougM

Persönlich denke ich, dass Domain Driven Design dazu beiträgt, Klarheit in dieses Problem zu bringen. Die Frage, die ich stelle, ist, wie ich das Kartenspiel den Menschen beschreibe. Mit anderen Worten, was modelliere ich? Wenn das, was ich modelliere, wirklich das Word-Ansichtsfenster und ein Konzept enthält, das seinem Verhalten entspricht, würde ich das Ansichtsfensterobjekt erstellen und es tun lassen, was es logisch sollte.

Wenn ich jedoch nicht das Konzept des Ansichtsfensters in meinem Spiel habe und es etwas ist, von dem ich denke, dass ich es brauche, weil sich der Code sonst "falsch anfühlt". Ich denke zweimal darüber nach, mein Domain-Modell hinzuzufügen.

Das Word-Modell bedeutet, dass Sie eine Darstellung von etwas erstellen. Ich warne davor, eine Klasse einzurichten, die etwas Abstraktes darstellt, das über das hinausgeht, was Sie darstellen.

Ich werde bearbeiten, um hinzuzufügen, dass es möglich ist, dass Sie das Konzept eines Ansichtsfensters in einem anderen Teil Ihres Codes benötigen, wenn Sie eine Schnittstelle mit einer Anzeige benötigen. In DDD-Begriffen wäre dies jedoch ein Infrastrukturproblem und würde außerhalb des Domänenmodells existieren.

8
RibaldEddie

Normalerweise mache ich keine Eigenwerbung, aber Tatsache ist, dass ich viel über OOP Designprobleme auf mein Blog geschrieben habe. Um mehrere Seiten zusammenzufassen: Sie sollten nicht Beginnen Sie das Design nicht mit Klassen. Wenn Sie mit Schnittstellen oder APIs und Formcode beginnen, haben Sie höhere Chancen, aussagekräftige Abstraktionen bereitzustellen, Spezifikationen anzupassen und das Aufblähen konkreter Klassen mit nicht wiederverwendbarem Code zu vermeiden.

Wie dies auf das Problem Card-Player zutrifft: Das Erstellen einer Abstraktion ViewPort ist sinnvoll, wenn Sie Card und Player als zwei betrachten unabhängige Bibliotheken (was bedeuten würde, dass Player manchmal ohne Card verwendet wird). Ich bin jedoch geneigt zu glauben, dass ein PlayerCards enthält und ihnen einen Collection<Card> getVisibleCards () Accessor bereitstellen sollte. Diese beiden Lösungen (ViewPort und meine) sind besser als die Bereitstellung von isVisible als Methode für Card oder Player, um verständliche Codebeziehungen zu erstellen.

Eine Lösung außerhalb der Klasse ist für DocumentId viel, viel besser. Es gibt wenig Motivation, (im Grunde genommen eine Ganzzahl) von einer komplexen Datenbankbibliothek abhängig zu machen.

5
Arthur Havlicek

Ich bin nicht sicher, ob die vorliegende Frage auf der richtigen Ebene beantwortet wird. Ich hatte die Weisen im Forum aufgefordert, hier aktiv über den Kern der Frage nachzudenken.

U Mad spricht eine Situation an, in der er glaubt, dass die Programmierung nach seinem Verständnis von OOP) im Allgemeinen dazu führen würde, dass viele Blattknoten Dateninhaber sind, während seine API der oberen Ebene den größten Teil der Daten umfasst Verhalten.

Ich denke, das Thema ging ein wenig tangential dahin, ob isVisible auf Card vs Player definiert werden würde. es war nur ein Beispiel, wenn auch naiv.

Ich hatte Push den erfahrenen hier, um das Problem zur Hand zu sehen. Ich denke, es gibt eine gute Frage, auf die U Mad gedrängt hat. Ich verstehe, dass Sie die Regeln und die betroffene Logik auf ein eigenes Objekt übertragen würden; aber wie ich verstehe ist die frage

  1. Ist es in Ordnung, einfache Datenhalterkonstrukte (Klassen/Strukturen; es ist mir egal, wie sie für diese Frage modelliert werden) zu haben, die nicht wirklich viel Funktionalität bieten?
  2. Wenn ja, wie können sie am besten oder am besten modelliert werden?
  3. Wenn nein, wie integrieren wir diese Datenzählerteile in höhere API-Klassen (einschließlich Verhalten)?

Meine Sicht:

Ich denke, Sie stellen eine Frage der Granularität, die in der objektorientierten Programmierung nur schwer richtig zu stellen ist. Nach meiner kleinen Erfahrung würde ich keine Entität in mein Modell aufnehmen, die kein Verhalten für sich enthält. Wenn ich muss, hätte ich wahrscheinlich ein Konstrukt verwendet, eine Struktur, die eine solche Abstraktion enthält, im Gegensatz zu einer Klasse, die die Idee hat, Daten und Verhalten zu kapseln.

3
Harsha

Eine häufige Quelle der Verwirrung in OOP) ist die Tatsache, dass viele Objekte zwei Aspekte des Zustands enthalten: die Dinge, über die sie Bescheid wissen, und die Dinge, die über sie Bescheid wissen. Diskussionen über den Zustand von Objekten werden häufig ignoriert Letzterer Aspekt, da es in Frameworks, in denen Objektreferenzen promiskuitiv sind, keine allgemeine Möglichkeit gibt, zu bestimmen, was Dinge über ein Objekt wissen könnten, dessen Referenz jemals der Außenwelt ausgesetzt war.

Ich würde vorschlagen, dass es wahrscheinlich hilfreich wäre, ein CardEntity -Objekt zu haben, das diese Aspekte der Karte in separaten Komponenten zusammenfasst. Eine Komponente würde sich auf die Markierungen auf der Karte beziehen (z. B. "Diamond King" oder "Lava Blast; Spieler haben AC-3-Chance auszuweichen oder 2D6-Schaden zu erleiden"). Man könnte sich auf einen einzigartigen Aspekt des Zustands beziehen, wie z. B. die Position (z. B. im Deck, in Joes Hand oder auf dem Tisch vor Larry). Ein dritter könnte sich darauf beziehen, es zu sehen (vielleicht niemand, vielleicht ein Spieler oder vielleicht viele Spieler). Um sicherzustellen, dass alles synchron bleibt, werden Orte, an denen sich eine Karte möglicherweise befindet, nicht als einfache Felder, sondern als CardSpace Objekte gekapselt. Um eine Karte in ein Feld zu verschieben, würde man ihr einen Verweis auf das richtige CardSpace Objekt geben. es würde sich dann aus dem alten Raum entfernen und sich in den neuen Raum versetzen).

Das explizite Einkapseln von "Wer weiß über X" getrennt von "Was X weiß" sollte helfen, viel Verwirrung zu vermeiden. Manchmal ist Vorsicht geboten, um Speicherlecks zu vermeiden, insbesondere bei vielen Assoziationen (z. B. wenn neue Karten entstehen und alte Karten verschwinden können, muss sichergestellt werden, dass Karten, die aufgegeben werden sollten, nicht ständig an langlebigen Objekten befestigt bleiben ) Wenn jedoch das Vorhandensein von Verweisen auf ein Objekt einen relevanten Teil seines Zustands bildet, ist es völlig richtig, dass das Objekt selbst solche Informationen explizit kapselt (selbst wenn es die Arbeit der tatsächlichen Verwaltung an eine andere Klasse delegiert).

2
supercat

Dieser Ansatz beraubt jedoch sowohl die Karten- als auch die Spielerklasse einer möglichen Mitgliedsfunktion.

Und wie ist das schlecht/schlecht beraten?

Um eine ähnliche Analogie zu Ihrem Kartenbeispiel zu verwenden, betrachten Sie ein Car, ein Driver und Sie müssen bestimmen, ob das Driver das Car steuern kann.

OK, Sie haben entschieden, dass Ihr Car nicht wissen soll, ob der Driver den richtigen Autoschlüssel hat oder nicht, und aus einem unbekannten Grund haben Sie auch entschieden, dass Sie Ihren nicht wollen Driver, um mehr über die Klasse Car zu erfahren (Sie haben dies auch in Ihrer ursprünglichen Frage nicht vollständig konkretisiert). Daher haben Sie eine Zwischenklasse, ähnlich einer Utils -Klasse, die die Methode mit Geschäftsregeln enthält, um einen boolean -Wert für die zurückzugeben Frage oben.

Ich denke das ist in Ordnung. Die Zwischenklasse muss möglicherweise nur noch nach Autoschlüsseln suchen, kann jedoch überarbeitet werden, um zu prüfen, ob der Fahrer einen gültigen Führerschein besitzt, unter Alkoholeinfluss steht oder in dystopischer Zukunft nach DNA-Biometrie sucht. Durch die Einkapselung gibt es wirklich keine großen Probleme, wenn diese drei Klassen zusammen existieren.

0
h.j.k.