Beziehung zwischen Objekten

8

Seit einigen Wochen denke ich über die Beziehung zwischen Objekten nach - nicht besonders über OOPs Objekte. In C ++ sind wir es beispielsweise gewohnt, dies darzustellen, indem Zeiger oder Container mit Zeigern in der Struktur geschichtet werden, die einen Zugriff auf das andere Objekt benötigen. Wenn ein Objekt AZugriff haben muss, Bist es nicht ungewöhnlich, ein B *pBIn zu finden A.

Aber ich bin kein C ++ - Programmierer mehr, ich schreibe Programme mit funktionalen Sprachen, insbesondere in Haskell , einer reinen funktionalen Sprache. Es ist möglich, Zeiger, Referenzen oder ähnliches zu verwenden, aber ich finde das seltsam, wie „es nicht nach Haskell zu machen“.

Dann dachte ich etwas tiefer über all diese Beziehungssachen nach und kam auf den Punkt:

„Warum repräsentieren wir eine solche Beziehung überhaupt durch Schichtung?

Ich habe einige Leute gelesen, die bereits darüber nachgedacht haben ( hier ). Aus meiner Sicht ist die Darstellung von Beziehungen durch explizite Diagramme viel besser, da wir uns auf den Kern unseres Typs konzentrieren und Beziehungen später durch Kombinatoren ausdrücken können (ähnlich wie bei SQL ).

Mit Kern meine ich, dass wir beim Definieren Aerwarten, zu definieren, woraus Aes besteht , und nicht, wovon es abhängt . Wenn wir zum Beispiel in einem Videospiel einen Typ haben Character, ist es legitim, darüber zu sprechen Trait, Skilloder über solche Dinge, aber ist es, wenn wir über Weaponoder sprechen Items? Ich bin mir nicht mehr so ​​sicher. Dann:

data Character = {
    chSkills :: [Skill]
  , chTraits :: [Traits]
  , chName   :: String
  , chWeapon :: IORef Weapon -- or STRef, or whatever
  , chItems  :: IORef [Item] -- ditto
  }

klingt für mich wirklich falsch im Design. Ich würde lieber etwas bevorzugen wie:

data Character = {
    chSkills :: [Skill]
  , chTraits :: [Traits]
  , chName   :: String
  }

-- link our character to a Weapon using a Graph Character Weapon
-- link our character to Items using a Graph Character [Item] or that kind of stuff

Wenn ein Tag kommt, um neue Funktionen hinzuzufügen, können wir einfach neue Typen, neue Diagramme und Verknüpfungen erstellen. Im ersten Entwurf müssten wir den CharacterTyp brechen oder irgendeine Art von Arbeit verwenden, um ihn zu erweitern.

Was denkst du über diese Idee? Was ist Ihrer Meinung nach am besten, um diese Art von Problemen in Haskell , einer rein funktionalen Sprache , zu lösen ?

Phaazon
quelle
2
Meinungsumfragen sind hier nicht erlaubt. Sie sollten die Frage wie folgt umformulieren: "Gibt es Nachteile bei diesem Ansatz?" statt "was denkst du darüber?" oder "was ist der beste Weg zu ...?" Was auch immer es wert ist, es macht keinen Sinn zu fragen, warum Dinge in C ++ auf eine bestimmte Art und Weise erledigt werden, im Gegensatz zu der Art und Weise, wie es in funktionalen Sprachen gemacht wird, da C ++ keine Speicherbereinigung hat, alles analog zu Typklassen (es sei denn, Sie verwenden Vorlagen). Dies öffnet eine wirklich große Dose Würmer) und viele andere Funktionen, die die funktionale Programmierung erleichtern.
Doval
Eine Sache, die nichts wert ist, ist, dass es bei Ihrem Ansatz nicht möglich ist, mithilfe des Typsystems zu sagen , ob a Character in der Lage sein sollte, Waffen zu halten. Wenn Sie eine Karte von Charactersbis haben Weaponsund ein Charakter nicht auf der Karte vorhanden ist, bedeutet dies, dass der Charakter derzeit keine Waffe besitzt oder dass der Charakter überhaupt keine Waffen halten kann? Außerdem würden Sie unnötige Suchvorgänge durchführen, da Sie nicht a priori wissen, ob a Charactereine Waffe halten kann oder nicht.
Doval
@Doval Wenn Charaktere, die keine Waffen halten können, eine Funktion in Ihrem Spiel waren, wäre es nicht sinnvoll, dem Charakterdatensatz eine canHoldWeapons :: BoolFlagge zu geben, die Sie sofort darüber informiert, ob er Waffen halten kann und ob Ihr Charakter dies nicht ist In der Grafik können Sie sagen, dass der Charakter derzeit keine Waffen hält. Machen Sie giveCharacterWeapon :: WeaponGraph -> Character -> Weapon -> WeaponGrapheinfach so, als idob dieses Flag Falsefür das angegebene Zeichen ist, oder verwenden Sie es Maybe, um einen Fehler darzustellen.
Bheklilr
Ich mag das und denke ehrlich gesagt, dass das, was Sie entwerfen, sehr gut zu einem Anwendungsstil passt - was ich häufig finde, wenn ich OO-Modellierungsprobleme in Haskell mache. Mithilfe von Anwendungen können Sie Verhaltensweisen im CRUD-Stil gut und flüssig zusammenstellen, sodass Sie die Idee haben, Bits unabhängig voneinander zu modellieren und dann einige Anwendungsimplementierungen zu erstellen, mit denen Sie diese Typen zusammenstellen können, wobei Operationen zwischen den Kompositionen für Dinge wie Validierung, Persistenz und reaktives Ereignis verschoben werden Brennen usw. Dies passt sehr gut zu dem, was Sie dort vorschlagen. Grafiken eignen sich hervorragend für Anwendungen.
Jimmy Hoffa
3
@Doval Speziell bei Datentypen im Datensatzstil ist der folgende Code bei mehreren Konstruktoren vollkommen legal und gibt bei -WallGHC keine Warnungen aus : data Foo = Foo { foo :: Int } | Bar { bar :: String }. Anschließend werden Checks zum Aufrufen von foo (Bar "")oder eingegeben bar (Foo 0), aber beide lösen Ausnahmen aus. Wenn Sie Positionsstil-Datenkonstruktoren verwenden, gibt es kein Problem, da der Compiler die Accessoren nicht für Sie generiert, Sie jedoch nicht die Bequemlichkeit der Datensatztypen für große Strukturen erhalten und die Verwendung von mehr Boilerplate erforderlich ist Bibliotheken wie Objektiv.
bheklilr

Antworten:

2

Sie haben tatsächlich Ihre eigene Frage beantwortet, Sie wissen es nur noch nicht. Die Frage, die Sie stellen, betrifft nicht Haskell, sondern die Programmierung im Allgemeinen. Sie stellen sich tatsächlich sehr gute Fragen (Kudos).

Meine Herangehensweise an das Problem, das Sie zur Hand haben, gliedert sich im Wesentlichen in zwei Hauptaspekte: das Design des Domänenmodells und die Kompromisse.

Lassen Sie mich erklären.

Domänenmodelldesign : So entscheiden Sie sich, das Kernmodell Ihrer Anwendung zu organisieren. Ich kann sehen, dass Sie das bereits tun, indem Sie an Charaktere, Waffen usw. denken. Außerdem müssen Sie die Beziehungen zwischen ihnen definieren. Ihr Ansatz, Objekte nach dem zu definieren, was sie sind, anstatt nach dem, wovon sie abhängen, ist absolut gültig. Seien Sie jedoch vorsichtig, denn es ist nicht bei jeder Entscheidung bezüglich Ihres Designs eine Silberkugel.

Sie tun definitiv das Richtige, indem Sie im Voraus darüber nachdenken, aber irgendwann müssen Sie aufhören zu denken und anfangen, Code zu schreiben. Sie werden dann sehen, ob Ihre frühen Entscheidungen die richtigen waren oder nicht. Höchstwahrscheinlich nicht, da Sie Ihre Anwendung noch nicht vollständig kennen. Dann haben Sie keine Angst vor Refactoring, wenn Sie feststellen, dass bestimmte Entscheidungen zu einem Problem und nicht zu einer Lösung werden. Es ist wichtig, Code zu schreiben, wenn Sie über diese Entscheidungen nachdenken, damit Sie sie validieren und vermeiden können, dass Sie das Ganze neu schreiben müssen.

Es gibt mehrere gute Prinzipien, denen Sie folgen können. Suchen Sie einfach bei Google nach "Software-Prinzipien", und Sie werden eine Reihe davon finden.

Kompromisse : Alles ist ein Kompromiss. Einerseits ist es schlecht, zu viele Abhängigkeiten zu haben, aber Sie müssen sich mit der zusätzlichen Komplexität der Verwaltung von Abhängigkeiten an einem anderen Ort als in Ihren Domänenobjekten auseinandersetzen. Es gibt keine richtige Entscheidung. Wenn Sie ein Bauchgefühl haben, machen Sie es. Sie werden viel lernen, wenn Sie diesen Weg gehen und das Ergebnis sehen.

Wie ich schon sagte, Sie stellen die richtigen Fragen und das ist das Wichtigste. Persönlich stimme ich Ihrer Argumentation zu, aber es sieht so aus, als hätten Sie bereits zu viel Zeit darauf verwendet, darüber nachzudenken. Hören Sie einfach auf, Angst vor Fehlern zu haben, und machen Sie Fehler . Sie sind wirklich wertvoll und Sie können Ihren Code jederzeit umgestalten, wann immer Sie dies für richtig halten.

Alex
quelle
Vielen Dank für Ihre Antwort. Seit ich das gepostet habe, bin ich viele Wege gegangen. Ich habe AST ausprobiert, AST gebunden, freie Monaden, jetzt versuche ich, das Ganze durch Typklassen darzustellen . In der Tat gibt es keine absolute Lösung. Ich habe nur das Bedürfnis, die gemeinsame Art der Beziehung in Frage zu stellen, die für mich alles andere als robust und flexibel ist.
Phaazon
@phaazon Das ist großartig und ich wünschte, mehr Entwickler hätten einen ähnlichen Ansatz. Experimentieren ist das beste Lernwerkzeug. Viel Glück bei Ihrem Projekt.
Alex