Questa è una trascrizione più ben definita del mio commento iniziale sotto la tua domanda. Le risposte alle domande poste dal PO possono essere trovate in fondo a questa risposta. Controlla anche la nota importante che si trova nello stesso posto.
Quello che stai attualmente descrivendo, Sipo, è un modello di progettazione chiamato Record attivo . Come per tutto, anche questo ha trovato il suo posto tra i programmatori, ma è stato scartato a favore del repository e dei modelli di mappatura dei dati per un semplice motivo, la scalabilità.
In breve, un record attivo è un oggetto che:
- rappresenta un oggetto nel tuo dominio (include le regole aziendali, sa come gestire determinate operazioni sull'oggetto, ad esempio se puoi o non puoi cambiare un nome utente e così via),
- sa come recuperare, aggiornare, salvare ed eliminare l'entità.
Affronti diversi problemi con il tuo progetto attuale e il problema principale del tuo progetto è affrontato nell'ultimo, sesto punto (ultimo ma non meno importante, suppongo). Quando hai una classe per la quale stai progettando un costruttore e non sai nemmeno cosa dovrebbe fare il costruttore, probabilmente la classe sta facendo qualcosa di sbagliato. È successo nel tuo caso.
Ma riparare il progetto è in realtà piuttosto semplice suddividendo la rappresentazione dell'entità e la logica CRUD in due (o più) classi.
Ecco come appare il tuo design ora:
Employee
- contiene informazioni sulla struttura del dipendente (i suoi attributi) e i metodi su come modificare l'entità (se si decide di procedere in modo mutevole), contiene la logica CRUD per l' Employee
entità, può restituire un elenco di Employee
oggetti, accettare un Employee
oggetto quando si desidera aggiornare un dipendente, può restituire un singolo Employee
tramite un metodo comegetSingleById(id : string) : Employee
Caspita, la classe sembra enorme.
Questa sarà la soluzione proposta:
Employee
- contiene informazioni sulla struttura dei dipendenti (i suoi attributi) e sui metodi per modificare l'entità (se si decide di procedere in modo mutevole)
EmployeeRepository
- contiene la logica CRUD per l' Employee
entità, può restituire un elenco di Employee
oggetti, accetta un Employee
oggetto quando si desidera aggiornare un dipendente, può restituire un singolo Employee
tramite un metodo comegetSingleById(id : string) : Employee
Hai sentito della separazione delle preoccupazioni ? No, lo farai ora. È la versione meno rigorosa del principio di responsabilità singola, che afferma che una classe dovrebbe effettivamente avere una sola responsabilità, o come dice lo zio Bob:
Un modulo dovrebbe avere una e una sola ragione per cambiare.
È abbastanza chiaro che se fossi stato in grado di dividere chiaramente la tua classe iniziale in due che hanno ancora un'interfaccia ben arrotondata, probabilmente la classe iniziale stava facendo troppo, e lo era.
Ciò che è bello del modello di repository, non solo funge da astrazione per fornire uno strato intermedio tra il database (che può essere qualsiasi cosa, file, noSQL, SQL, orientato agli oggetti), ma non deve nemmeno essere concreto classe. In molti linguaggi OO, è possibile definire l'interfaccia come effettiva interface
(o una classe con un metodo virtuale puro se si è in C ++) e quindi avere più implementazioni.
Questo solleva completamente la decisione se un repository è un'implementazione effettiva di te semplicemente basandosi sull'interfaccia facendo affidamento su una struttura con la interface
parola chiave. E il repository è esattamente questo, è un termine sofisticato per l'astrazione del livello dati, vale a dire mappare i dati sul tuo dominio e viceversa.
Un'altra cosa grandiosa di separarlo in (almeno) due classi è che ora la Employee
classe può chiaramente gestire i propri dati e farlo molto bene, perché non ha bisogno di occuparsi di altre cose difficili.
Domanda 6: Quindi cosa dovrebbe fare il costruttore nella Employee
classe appena creata ? È semplice. Dovrebbe prendere gli argomenti, verificare se sono validi (come un'età probabilmente non dovrebbe essere negativa o il nome non dovrebbe essere vuoto), generare un errore quando i dati non erano validi e se la convalida passata assegna gli argomenti alle variabili private dell'entità. Ora non è in grado di comunicare con il database, perché semplicemente non ha idea di come farlo.
Domanda 4: non è possibile rispondere a tutti, non in generale, perché la risposta dipende fortemente da ciò di cui hai esattamente bisogno.
Domanda 5: Ora che avete separato la classe gonfio in due, è possibile avere più metodi di aggiornamento direttamente sulla Employee
classe, come changeUsername
, markAsDeceased
, che manipolare i dati della Employee
classe solo nella RAM e quindi si potrebbe introdurre un metodo come registerDirty
dalla Modello di unità di lavoro per la classe repository, tramite la quale si comunica al repository che questo oggetto ha modificato le proprietà e dovrà essere aggiornato dopo aver chiamato il commit
metodo.
Ovviamente, per un aggiornamento un oggetto richiede di avere un ID e quindi essere già salvato, ed è responsabilità del repository rilevare questo e generare un errore quando i criteri non sono soddisfatti.
Domanda 3: se decidi di seguire il modello Unità di lavoro, il create
metodo sarà ora registerNew
. Se non lo fai, probabilmente lo chiamerei save
invece. L'obiettivo di un repository è di fornire un'astrazione tra il dominio e il livello dati, per questo ti consiglierei che questo metodo (sia esso registerNew
o save
) accetta l' Employee
oggetto e spetta alle classi che implementano l'interfaccia del repository, quali attributi decidono di togliere dall'entità. Passare un intero oggetto è meglio, quindi non è necessario disporre di molti parametri opzionali.
Domanda 2: Entrambi i metodi faranno ora parte dell'interfaccia del repository e non violano il principio della responsabilità singola. La responsabilità del repository è quella di fornire operazioni CRUD per gli Employee
oggetti, questo è ciò che fa (oltre a Leggi ed Elimina, CRUD si traduce sia in Crea sia in Aggiorna). Ovviamente, potresti dividere ulteriormente il repository avendo un EmployeeUpdateRepository
e così via, ma ciò è raramente necessario e una singola implementazione di solito può contenere tutte le operazioni CRUD.
Domanda 1: hai finito con una semplice Employee
classe che ora (tra gli altri attributi) avrà id. Se l'id è pieno o vuoto (o null
) dipende dal fatto che l'oggetto sia già stato salvato. Tuttavia, un ID è ancora un attributo dell'entità e la responsabilità Employee
dell'entità è quella di prendersi cura dei suoi attributi, quindi prendersi cura del suo ID.
Il fatto che un'entità abbia o meno un ID di solito non ha importanza fino a quando non si tenta di eseguire una logica di persistenza su di essa. Come indicato nella risposta alla domanda 5, è responsabilità del repository rilevare che non si sta tentando di salvare un'entità che è già stata salvata o che si sta tentando di aggiornare un'entità senza un ID.
Nota importante
Si prega di essere consapevoli del fatto che sebbene la separazione delle preoccupazioni sia grande, in realtà progettare un livello di repository funzionale è un lavoro piuttosto noioso e nella mia esperienza è un po 'più difficile da ottenere rispetto all'approccio di registrazione attiva. Ma finirai con un design che è molto più flessibile e scalabile, il che può essere una buona cosa.
Employee
oggetto per fornire astrazione, le domande 4. e 5. sono generalmente senza risposta, dipendono dalle tue esigenze e se separi la struttura e le operazioni CRUD in due classi, allora è abbastanza chiaro, il costruttore delEmployee
non può recuperare i dati da db più, in modo che risponda 6.