Dies ist eine wohlgeformtere Transkription meines ersten Kommentars unter Ihrer Frage. Die Antworten auf Fragen, die vom OP beantwortet werden, finden Sie am Ende dieser Antwort. Bitte überprüfen Sie auch den wichtigen Hinweis am selben Ort.
Was Sie derzeit beschreiben, Sipo, ist ein Entwurfsmuster namens Active Record . Wie bei allem hat auch dieser seinen Platz unter den Programmierern gefunden, wurde jedoch aus einem einfachen Grund, der Skalierbarkeit, zugunsten des Repositorys und der Data Mapper- Muster verworfen .
Kurz gesagt, ein aktiver Datensatz ist ein Objekt, das:
- stellt ein Objekt in Ihrer Domäne dar (enthält Geschäftsregeln, weiß, wie bestimmte Vorgänge für das Objekt ausgeführt werden, z. B. ob Sie einen Benutzernamen ändern können oder nicht usw.),
- weiß, wie die Entität abgerufen, aktualisiert, gespeichert und gelöscht wird.
Sie sprechen mehrere Probleme mit Ihrem aktuellen Design an, und das Hauptproblem Ihres Designs wird im letzten, sechsten Punkt angesprochen (last but not least, denke ich). Wenn Sie eine Klasse haben, für die Sie einen Konstruktor entwerfen, und Sie nicht einmal wissen, was der Konstruktor tun soll, macht die Klasse wahrscheinlich etwas falsch. Das ist in deinem Fall passiert.
Das Korrigieren des Entwurfs ist jedoch ziemlich einfach, indem die Entitätsdarstellung und die CRUD-Logik in zwei (oder mehr) Klassen aufgeteilt werden.
So sieht Ihr Design jetzt aus:
Employee
- enthält Informationen über die Mitarbeiterstruktur (ihre Attribute) und Methoden zum Ändern der Entität (wenn Sie sich für einen veränderlichen Weg entscheiden), enthält CRUD-Logik für die Employee
Entität, kann eine Liste von Employee
Objekten zurückgeben und akzeptiert ein Employee
Objekt, wenn Sie möchten Aktualisieren Sie einen Mitarbeiter, kann eine einzelne Employee
über eine Methode wie zurückgebengetSingleById(id : string) : Employee
Wow, die Klasse scheint riesig.
Dies wird die vorgeschlagene Lösung sein:
Employee
- enthält Informationen über die Mitarbeiterstruktur (ihre Attribute) und Methoden zum Ändern der Entität (wenn Sie sich für einen veränderlichen Weg entscheiden)
EmployeeRepository
- enthält CRUD-Logik für die Employee
Entität, kann eine Liste von Employee
Objekten zurückgeben, akzeptiert ein Employee
Objekt, wenn Sie einen Mitarbeiter aktualisieren möchten, kann ein einzelnes Employee
über eine Methode wie zurückgebengetSingleById(id : string) : Employee
Haben Sie von der Trennung von Bedenken gehört ? Nein, das wirst du jetzt. Es ist die weniger strenge Version des Prinzips der Einzelverantwortung, die besagt, dass eine Klasse eigentlich nur eine Verantwortung haben sollte, oder wie Onkel Bob sagt:
Ein Modul sollte nur einen Grund zur Änderung haben.
Es ist ziemlich klar, dass, wenn ich Ihre anfängliche Klasse klar in zwei Klassen aufteilen konnte, die immer noch eine gut abgerundete Oberfläche haben, die anfängliche Klasse wahrscheinlich zu viel getan hat, und das war es auch.
Das Besondere am Repository-Muster ist, dass es nicht nur als Abstraktion dient, um eine mittlere Schicht zwischen der Datenbank (die alles sein kann, Datei, NoSQL, SQL, objektorientiert) bereitzustellen, sondern auch nicht konkret sein muss Klasse. In vielen OO-Sprachen können Sie die Schnittstelle als eine tatsächliche interface
(oder eine Klasse mit einer rein virtuellen Methode, wenn Sie sich in C ++ befinden) definieren und dann mehrere Implementierungen durchführen.
Dies hebt die Entscheidung, ob ein Repository eine tatsächliche Implementierung von Ihnen ist, vollständig auf. Sie verlassen sich einfach auf die Schnittstelle, indem Sie sich tatsächlich auf eine Struktur mit dem interface
Schlüsselwort verlassen. Und Repository ist genau das. Es ist ein ausgefallener Begriff für die Abstraktion von Datenschichten, nämlich das Zuordnen von Daten zu Ihrer Domain und umgekehrt.
Eine weitere großartige Sache bei der Aufteilung in (mindestens) zwei Klassen ist, dass die Employee
Klasse jetzt ihre eigenen Daten klar verwalten und es sehr gut machen kann, da sie sich nicht um andere schwierige Dinge kümmern muss.
Frage 6: Was soll der Konstruktor in der neu erstellten Employee
Klasse tun ? Es ist einfach. Es sollte die Argumente übernehmen, prüfen, ob sie gültig sind (z. B. sollte ein Alter wahrscheinlich nicht negativ sein oder der Name sollte nicht leer sein), einen Fehler auslösen, wenn die Daten ungültig waren und wenn die bestandene Validierung die Argumente privaten Variablen zuweist des Unternehmens. Es kann jetzt nicht mit der Datenbank kommunizieren, da es einfach keine Ahnung hat, wie es geht.
Frage 4: Kann überhaupt nicht beantwortet werden, nicht generell, da die Antwort stark davon abhängt, was genau Sie brauchen.
Frage 5: Nun , da Sie die aufgeblähte Klasse in zwei getrennt haben, können Sie auf der mehrere Update - Methoden direkt haben Employee
Klasse, wie changeUsername
, markAsDeceased
, die die Daten der Manipulation Employee
Klasse nur im RAM und dann könnte man ein Verfahren , wie zum Beispiel vorstellt registerDirty
von der Arbeitseinheitsmuster für die Repository-Klasse, über das Sie dem Repository mitteilen würden, dass dieses Objekt Eigenschaften geändert hat und nach dem Aufrufen der commit
Methode aktualisiert werden muss .
Offensichtlich muss ein Objekt für ein Update eine ID haben und daher bereits gespeichert sein. Es ist die Verantwortung des Repositorys, dies zu erkennen und einen Fehler auszulösen, wenn die Kriterien nicht erfüllt sind.
Frage 3: Wenn Sie sich für das Arbeitseinheitsmuster entscheiden, lautet die create
Methode jetzt registerNew
. Wenn Sie dies nicht tun, würde ich es wahrscheinlich save
stattdessen nennen. Das Ziel eines Repositorys ist es, eine Abstraktion zwischen der Domäne und der Datenschicht bereitzustellen. Aus diesem Grund würde ich Ihnen empfehlen, dass diese Methode (sei es registerNew
oder save
) das Employee
Objekt akzeptiert und es den Klassen überlassen bleibt, die die Repository-Schnittstelle implementieren, welche Attribute Sie beschließen, das Unternehmen zu verlassen. Das Übergeben eines gesamten Objekts ist besser, sodass Sie nicht viele optionale Parameter benötigen.
Frage 2: Beide Methoden werden nun Teil der Repository-Schnittstelle und verstoßen nicht gegen das Prinzip der Einzelverantwortung. Die Verantwortung des Repositorys besteht darin, CRUD-Operationen für die Employee
Objekte bereitzustellen. Dies ist die Aufgabe (neben Lesen und Löschen übersetzt CRUD sowohl Erstellen als auch Aktualisieren). Natürlich könnten Sie das Repository noch weiter aufteilen, indem Sie ein EmployeeUpdateRepository
und so weiter haben, aber das wird selten benötigt und eine einzelne Implementierung kann normalerweise alle CRUD-Operationen enthalten.
Frage 1: Sie haben eine einfache Employee
Klasse erhalten, die jetzt (unter anderem) die ID hat. Ob die ID gefüllt oder leer (oder null
) ist, hängt davon ab, ob das Objekt bereits gespeichert wurde. Trotzdem ist eine ID immer noch ein Attribut, das die Entität besitzt, und die Verantwortung der Employee
Entität besteht darin, sich um ihre Attribute und damit um ihre ID zu kümmern.
Ob eine Entität eine ID hat oder nicht, spielt normalerweise keine Rolle, bis Sie versuchen, eine Persistenzlogik darauf zu erstellen. Wie in der Antwort auf Frage 5 erwähnt, liegt es in der Verantwortung des Repositorys, festzustellen, dass Sie nicht versuchen, eine bereits gespeicherte Entität zu speichern oder eine Entität ohne ID zu aktualisieren.
Wichtige Notiz
Bitte beachten Sie, dass das Entwerfen einer funktionalen Repository-Schicht zwar eine große Trennung der Bedenken ist, aber eine ziemlich mühsame Arbeit ist und meiner Erfahrung nach etwas schwieriger zu erreichen ist als der Ansatz der aktiven Aufzeichnung. Am Ende erhalten Sie jedoch ein Design, das weitaus flexibler und skalierbarer ist, was eine gute Sache sein kann.
Employee
Objekt zur Abstraktion verwendet werden, sind die Fragen 4. und 5. im Allgemeinen nicht zu beantworten, hängen von Ihren Anforderungen ab. Wenn Sie die Struktur- und CRUD-Operationen in zwei Klassen unterteilen, ist es ziemlich klar, dass der Konstruktor vonEmployee
keine Daten abrufen kann von db mehr, so dass Antworten 6.