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 A
Zugriff haben muss, B
ist es nicht ungewöhnlich, ein B *pB
In 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 A
erwarten, zu definieren, woraus A
es 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
, Skill
oder über solche Dinge, aber ist es, wenn wir über Weapon
oder 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 Character
Typ 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 ?
quelle
Character
in der Lage sein sollte, Waffen zu halten. Wenn Sie eine Karte vonCharacters
bis habenWeapons
und 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 aCharacter
eine Waffe halten kann oder nicht.canHoldWeapons :: Bool
Flagge 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 SiegiveCharacterWeapon :: WeaponGraph -> Character -> Weapon -> WeaponGraph
einfach so, alsid
ob dieses FlagFalse
für das angegebene Zeichen ist, oder verwenden Sie esMaybe
, um einen Fehler darzustellen.-Wall
GHC keine Warnungen aus :data Foo = Foo { foo :: Int } | Bar { bar :: String }
. Anschließend werden Checks zum Aufrufen vonfoo (Bar "")
oder eingegebenbar (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.Antworten:
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.
quelle