To jest lepiej sformułowana transkrypcja mojego pierwszego komentarza do twojego pytania. Odpowiedzi na pytania skierowane przez PO można znaleźć na dole tej odpowiedzi. Sprawdź również ważną notatkę znajdującą się w tym samym miejscu.
To, co obecnie opisujesz, Sipo, to wzorzec projektowy o nazwie Aktywny rekord . Jak wszystko, nawet ten znalazł swoje miejsce wśród programistów, ale został odrzucony na rzecz repozytorium i wzorców mapowania danych z jednego prostego powodu - skalowalności.
Krótko mówiąc, aktywny rekord to obiekt, który:
- reprezentuje obiekt w Twojej domenie (obejmuje reguły biznesowe, wie, jak obsłużyć pewne operacje na obiekcie, na przykład czy możesz zmienić nazwę użytkownika itp.),
- umie odzyskać, zaktualizować, zapisać i usunąć encję.
W swoim obecnym projekcie rozwiązujesz kilka problemów, a główny problem twojego projektu jest omówiony w ostatnim, szóstym punkcie (chyba, że nie mniej ważne). Kiedy masz klasę, dla której projektujesz konstruktor, a nawet nie wiesz, co powinien robić konstruktor, klasa prawdopodobnie robi coś złego. Tak było w twoim przypadku.
Ale naprawienie projektu jest w rzeczywistości dość proste poprzez podzielenie reprezentacji encji i logiki CRUD na dwie (lub więcej) klasy.
Tak teraz wygląda Twój projekt:
Employee
- zawiera informacje o strukturze pracownika (jego atrybuty) i metody modyfikowania encji (jeśli zdecydujesz się na modyfikację), zawiera logikę CRUD dla Employee
encji, może zwrócić listę Employee
obiektów, akceptuje Employee
obiekt, gdy chcesz zaktualizować pracownika, może zwrócić jeden Employee
za pomocą metody podobnej dogetSingleById(id : string) : Employee
Wow, klasa wydaje się ogromna.
To będzie proponowane rozwiązanie:
Employee
- zawiera informacje o strukturze pracownika (jej atrybuty) i metodach modyfikacji encji (jeśli zdecydujesz się na zmienną metodę)
EmployeeRepository
- zawiera logikę CRUD dla Employee
encji, może zwrócić listę Employee
obiektów, akceptuje Employee
obiekt, gdy chcesz zaktualizować pracownika, może zwrócić pojedynczy Employee
za pomocą metody takiej jakgetSingleById(id : string) : Employee
Czy słyszałeś o rozdzieleniu obaw ? Nie, teraz będziesz. Jest to mniej rygorystyczna wersja zasady pojedynczej odpowiedzialności, która mówi, że klasa powinna faktycznie mieć tylko jedną odpowiedzialność, lub jak mówi wujek Bob:
Moduł powinien mieć tylko jeden powód do zmiany.
Jest całkiem jasne, że gdybym był w stanie wyraźnie podzielić twoją klasę początkową na dwie, które wciąż mają dobrze zaokrąglony interfejs, klasa początkowa prawdopodobnie robiła zbyt wiele i tak było.
Co jest wspaniałe we wzorcu repozytorium, działa nie tylko jako abstrakcja, zapewniając warstwę pośrednią między bazą danych (która może być dowolna, plikowa, bez SQL, SQL, obiektowa), ale nawet nie musi być konkretna klasa. W wielu językach OO możesz zdefiniować interfejs jako rzeczywisty interface
(lub klasę z czystą wirtualną metodą, jeśli jesteś w C ++), a następnie mieć wiele implementacji.
To całkowicie eliminuje decyzję, czy repozytorium jest faktyczną implementacją, polegając na interfejsie, polegając na strukturze ze interface
słowem kluczowym. A repozytorium jest dokładnie tym, jest to fantazyjny termin na abstrakcję warstwy danych, a mianowicie mapowanie danych na twoją domenę i odwrotnie.
Kolejną wielką rzeczą w dzieleniu go na (przynajmniej) dwie klasy jest to, że teraz Employee
klasa może wyraźnie zarządzać swoimi danymi i robić to bardzo dobrze, ponieważ nie musi zajmować się innymi trudnymi rzeczami.
Pytanie 6: Co zatem powinien zrobić konstruktor w nowo utworzonej Employee
klasie? To jest proste. Powinien wziąć argumenty, sprawdzić, czy są poprawne (np. Wiek prawdopodobnie nie powinien być ujemny lub nazwa nie powinna być pusta), zgłosić błąd, gdy dane są nieprawidłowe, a jeśli weryfikacja się powiedzie, przypisz argumenty do zmiennych prywatnych podmiotu. Nie może teraz komunikować się z bazą danych, ponieważ po prostu nie ma pojęcia, jak to zrobić.
Pytanie 4: Nie można w ogóle odpowiedzieć, nie ogólnie, ponieważ odpowiedź w dużej mierze zależy od tego, czego dokładnie potrzebujesz.
Pytanie 5: Teraz, gdy już oddzielił nadęty klasę na dwie części, można mieć wiele metod aktualizacji bezpośrednio na Employee
zajęciach, jak changeUsername
, markAsDeceased
, które będą manipulować danymi o Employee
klasie tylko w pamięci RAM , a następnie można wprowadzić metody takie jak registerDirty
z Wzorzec jednostki pracy do klasy repozytorium, przez który informujesz repozytorium, że ten obiekt zmienił właściwości i będzie wymagał aktualizacji po wywołaniu commit
metody.
Oczywiście w przypadku aktualizacji obiekt musi mieć identyfikator, a zatem być już zapisany, a repozytorium jest odpowiedzialne za wykrycie tego i zgłoszenie błędu, gdy kryteria nie są spełnione.
Pytanie 3: Jeśli zdecydujesz się zastosować wzór Jednostki Pracy, create
metoda będzie teraz registerNew
. Jeśli nie, prawdopodobnie nazwałbym to save
zamiast tego. Celem repozytorium jest zapewnienie abstrakcji między domeną a warstwą danych, dlatego zalecałbym, aby ta metoda (czy to registerNew
lub save
) akceptowała Employee
obiekt i to do klas implementujących interfejs repozytorium, który przypisuje atrybuty decydują się usunąć z bytu. Przekazanie całego obiektu jest lepsze, więc nie musisz mieć wielu opcjonalnych parametrów.
Pytanie 2: Obie metody będą teraz częścią interfejsu repozytorium i nie będą naruszać zasady pojedynczej odpowiedzialności. Zadaniem repozytorium jest zapewnienie operacji CRUD dla Employee
obiektów, czyli to, co robi (oprócz odczytu i usuwania CRUD przekłada się zarówno na tworzenie, jak i aktualizację). Oczywiście, możesz podzielić repozytorium jeszcze bardziej, mając EmployeeUpdateRepository
i tak dalej, ale jest to rzadko potrzebne i jedna implementacja może zwykle zawierać wszystkie operacje CRUD.
Pytanie 1: Skończyłeś z prostą Employee
klasą, która teraz (między innymi atrybutami) będzie miała id. To, czy identyfikator jest wypełniony czy pusty (lub null
) zależy od tego, czy obiekt został już zapisany. Niemniej jednak identyfikator jest nadal atrybutem, który posiada jednostka, a obowiązkiem Employee
jednostki jest dbanie o jej atrybuty, a tym samym dbanie o jej identyfikator.
To, czy jednostka ma identyfikator, czy nie, zwykle nie ma znaczenia, dopóki nie spróbujesz wykonać na nim logiki trwałości. Jak wspomniano w odpowiedzi na pytanie 5, repozytorium jest odpowiedzialne za wykrycie, że nie próbujesz zapisać encji, która została już zapisana, lub próbujesz zaktualizować encję bez identyfikatora.
Ważna uwaga
Należy pamiętać, że chociaż rozdzielenie problemów jest świetne, w rzeczywistości zaprojektowanie funkcjonalnej warstwy repozytorium jest dość żmudną pracą, a moim doświadczeniem jest nieco trudniejsze do uzyskania niż aktywne podejście do nagrywania. Ale skończysz z projektem, który jest znacznie bardziej elastyczny i skalowalny, co może być dobrą rzeczą.
Employee
obiekt w celu zapewnienia abstrakcji, pytania 4. i 5. są generalnie niemożliwe do odpowiedzi, zależą od twoich potrzeb, a jeśli podzielisz strukturę i operacje CRUD na dwie klasy, to jest całkiem jasne, konstruktorEmployee
nie może pobrać danych od db już, więc to odpowiada 6.