Jakie są ogólne wytyczne lub praktyczne zasady korzystania z obiektu specyficznego dla domeny w porównaniu do zwykłego ciągu lub liczby?
Przykłady:
- Klasa wiekowa a liczba całkowita?
- Klasa FirstName vs String?
- UniqueID vs String
- Klasa PhoneNumber vs String vs Long?
- Klasa DomainName vs String?
Myślę, że większość praktykujących OOP zdecydowanie powiedziałaby określone klasy dla PhoneNumber i DomainName. Im więcej zasad dotyczących tego, co czyni je ważnymi i jak je porównywać, łatwiej i bezpieczniej radzić sobie z prostymi klasami. Ale w przypadku pierwszych trzech jest więcej debat.
Nigdy nie spotkałem się z klasą „Age”, ale można argumentować, że ma to sens, biorąc pod uwagę, że musi być ona nieujemna (okej, wiem, że możesz się kłócić o ujemny wiek, ale to dobry przykład, że jest prawie równoważna prymitywnej liczbie całkowitej).
Ciąg znaków często reprezentuje „Imię”, ale nie jest doskonały, ponieważ pusty ciąg znaków jest prawidłowym ciągiem znaków, ale nie jest prawidłową nazwą. Porównanie zwykle odbywa się z pominięciem wielkości liter. Pewnie, że istnieją metody sprawdzania pustości, porównywania bez rozróżniania wielkości liter itp., Ale wymaga tego konsument.
Czy odpowiedź zależy od środowiska? Przede wszystkim interesuje mnie oprogramowanie o wysokiej wartości dla przedsiębiorstw, które będzie żyło i będzie utrzymywane przez ponad dekadę.
Być może zastanawiam się nad tym, ale naprawdę chciałbym wiedzieć, czy ktoś ma zasady dotyczące wyboru klasy zamiast prymitywnej.
people may be one or two years older in Asian reckoning than in the western age system
Age
klasy zamiast liczby całkowitej, gdy potrzebujesz dodatkowego zachowania zapewnianego przez taką klasę.Odpowiedzi:
Ogólna zasada mówi, że chcesz modelować swoją domenę w języku specyficznym dla domeny.
Zastanów się: dlaczego używamy liczby całkowitej? Równie łatwo możemy przedstawić wszystkie liczby całkowite za pomocą łańcuchów. Lub z bajtami.
Gdybyśmy programowali w języku agnostycznym domenowym, który zawierałby prymitywne typy liczb całkowitych i wieku, co byś wybrał?
To, co tak naprawdę sprowadza się do „prymitywnej” natury niektórych typów, to wypadek wyboru języka do naszej implementacji.
W szczególności liczby zwykle wymagają dodatkowego kontekstu. Wiek to nie tylko liczba, ale także wymiar (czas), jednostki (lata?), Reguły zaokrąglania! Łączenie wieków razem ma sens w taki sposób, że nie dodaje się wieku do pieniędzy.
Wyróżnienie typów pozwala nam modelować różnice między niezweryfikowanym adresem e-mail a zweryfikowanym adresem e-mail .
Przypadek przedstawienia tych wartości w pamięci jest jedną z najmniej interesujących części. Model domeny nie dba
CustomerId
o int, ani o String, ani o identyfikator UUID / GUID, ani o węzeł JSON. Chce tylko afordancji.Czy naprawdę obchodzi nas, czy liczby całkowite są dużymi czy małymi? Czy obchodzi nas, czy to,
List
co minęło, jest abstrakcją nad tablicą lub wykresem? Kiedy odkryjemy, że arytmetyka podwójnej precyzji jest nieefektywna i że musimy przejść na reprezentację zmiennoprzecinkową, czy model domeny powinien się tym przejmować ?Parnas w 1972 roku napisał
W pewnym sensie wprowadzane przez nas typy wartości specyficzne dla domeny to moduły, które izolują naszą decyzję o tym, jaka podstawowa reprezentacja danych powinna zostać użyta.
Zaletą jest więc modułowość - otrzymujemy projekt, w którym łatwiej jest zarządzać zakresem zmiany. Minusem jest koszt - więcej pracy wymaga stworzenie potrzebnych typów na zamówienie, wybór odpowiednich typów wymaga głębszego zrozumienia domeny. Ilość pracy wymagana do utworzenia modułu wartości będzie zależeć od lokalnego dialektu Blub .
Inne terminy w równaniu mogą obejmować oczekiwany czas życia rozwiązania (staranne modelowanie skryptów, które zostaną uruchomione, gdy będzie miał kiepski zwrot z inwestycji), jak blisko domeny jest do podstawowych kompetencji firmy.
Jednym szczególnym przypadkiem, który możemy rozważyć, jest komunikacja ponad granicami . Nie chcemy znajdować się w sytuacji, w której zmiany w jednej przenośnej jednostce wymagają skoordynowanych zmian z innymi mobilnymi jednostkami. Dlatego wiadomości zwykle koncentrują się bardziej na reprezentacjach, bez uwzględnienia niezmienników lub zachowań specyficznych dla domeny. Nie będziemy próbować komunikować „ta wartość musi być ściśle dodatnia” w formacie komunikatu, ale raczej komunikować jego reprezentację w sieci i zastosować weryfikację tej reprezentacji na granicy domeny.
źródło
Myślisz o abstrakcji i to świetnie. Jednak rzeczy, które twoja lista wydaje mi się w większości indywidualnymi atrybutami czegoś większego (oczywiście nie wszystkie tego samego).
To, co uważam za ważniejsze, to zapewnienie abstrakcji, która grupuje atrybuty razem i daje im odpowiedni dom, na przykład klasę Person, aby programista klienta nie musiał radzić sobie z wieloma atrybutami, a raczej z abstrakcją wyższego poziomu.
Po uzyskaniu tej abstrakcji niekoniecznie potrzebujesz dodatkowych abstrakcji dla atrybutów, które są tylko wartościami. Klasa Person może przechowywać datę urodzenia jako (pierwotną) datę, wraz z PhoneNumbers jako listą (prymitywnych) ciągów znaków i Nazwą jako (pierwotny) ciąg znaków.
Jeśli masz numer telefonu z kodem formatującym, teraz są to dwa atrybuty i zasługiwałaby na klasę dla numeru telefonu (ponieważ prawdopodobnie miałby również pewne zachowanie, takie jak tylko uzyskiwanie liczb vs. uzyskiwanie sformatowanego numeru telefonu).
Jeśli masz Nazwę jako wiele atrybutów (np. Prefiksy / tytuły, pierwszy, ostatni, sufiks), które zasługują na klasę (ponieważ teraz masz pewne zachowania, takie jak uzyskanie pełnej nazwy jako ciągu vs. uzyskiwanie mniejszych elementów, tytułu itp. .).
źródło
Powinieneś modelować tak, jak potrzebujesz, jeśli chcesz po prostu danych, możesz użyć typów prymitywnych, ale jeśli chcesz wykonać więcej operacji na tych danych, należy je modelować w określonych klasach, na przykład (zgadzam się z @kiwiron w jego komentarzu) Klasa wiekowa nie ma sensu, ale lepiej byłoby zastąpić ją klasą Data urodzenia i umieść tam te metody.
Zaletą modelowania tych pojęć w klasach jest wymuszenie bogactwa swojego modelu, na przykład, zamiast tego masz metodę, która akceptuje dowolną datę, użytkownik może wypełnić ją dowolną datą, datą rozpoczęcia II wojny światowej lub datą zakończenia zimnej wojny, z tych dat wciąż ważna jest data urodzenia. możesz zastąpić go datą urodzenia, dlatego w takim przypadku narzucisz metodę akceptowania daty urodzenia, aby nie akceptować żadnej daty.
W przypadku Imienia nie widzę wiele korzyści, także UniqueID, PhoneNumber trochę, możesz poprosić go o podanie kraju i regionu, na przykład ponieważ ogólnie PhoneNumber odnosi się do krajów i regionów, niektórzy operatorzy używają predefiniowanych numerów Sufiksów do klasyfikowania telefonu komórkowego , Office ... numery, dzięki czemu możesz dodać te metody do tej klasy, tak samo jak nazwa_domeny.
Aby uzyskać więcej informacji, polecam przyjrzeć się obiektom Value w DDD (Domain Driven Design).
źródło
To zależy od tego, czy masz zachowanie (które musisz zachować i / lub zmienić) związane z tymi danymi, czy nie.
Krótko mówiąc, jeśli jest to tylko fragment danych, który jest rzucany bez większego powiązania z zachowaniem, użyj prymitywnego typu lub, w przypadku nieco bardziej złożonych fragmentów, struktury danych (w dużej mierze bez zachowania) (np. Klasa zawierająca tylko metody pobierające i setery).
Jeśli z drugiej strony okaże się, że w celu podjęcia decyzji sprawdzasz lub manipulujesz tymi danymi w różnych miejscach kodu, miejscach często nie blisko siebie - następnie zamień je w odpowiedni obiekt poprzez zdefiniowanie klasy i umieszczenie w nim odpowiedniego zachowania jako metod. Jeśli z jakiegoś powodu uważasz, że zdefiniowanie takiej klasy nie ma sensu, alternatywą jest skonsolidowanie tego zachowania w pewnego rodzaju klasę „usługową”, która działa na tych danych.
źródło
Twoje przykłady wyglądają bardziej jak właściwości lub atrybuty czegoś innego. Oznacza to, że całkowicie uzasadnione byłoby rozpoczęcie od samego prymitywu. W pewnym momencie będziesz musiał użyć prymitywu, więc zwykle oszczędzam złożoność na czas, gdy jej naprawdę potrzebuję.
Załóżmy na przykład, że tworzę grę i nie muszę się martwić o starzenie się postaci. Zastosowanie do tego liczby całkowitej ma sens. Wiek można wykorzystać w prostych kontrolach, aby sprawdzić, czy postać może coś zrobić, czy nie.
Jak zauważyli inni, wiek może być skomplikowanym tematem w zależności od lokalizacji. Jeśli musisz pogodzić wiek w różnych lokalizacjach itp., Warto wprowadzić
Age
klasę, która maDateOfBirth
iLocale
jak właściwości. Ta klasa wieku może również obliczyć aktualny wiek w dowolnym znaczniku czasu.Istnieją więc powody, aby promować prymityw do klasy, ale są one bardzo specyficzne dla aplikacji. Oto kilka przykładów:
Zasadniczo, jeśli masz wiele reguł dotyczących traktowania wartości, które chcesz konsekwentnie stosować w całym projekcie, utwórz klasę. Jeśli możesz żyć z prostymi wartościami, ponieważ nie ma to tak naprawdę decydującego znaczenia dla funkcjonalności, użyj prymitywu.
źródło
Pod koniec dnia obiekt jest tylko świadkiem, że jakiś proces faktycznie został uruchomiony † .
Prymityw
int
oznacza, że niektóre procesy wyzerowały 4 bajty pamięci, a następnie przerzuciły bity niezbędne do przedstawienia liczby całkowitej w zakresie od -2 147 483 648 do 2 147 483 647; co nie jest mocnym stwierdzeniem na temat wieku. Podobnie (niepuste)System.String
oznacza, że niektóre bajty zostały przydzielone, a bity odwrócone, aby reprezentować sekwencję znaków Unicode w odniesieniu do niektórych kodowań.Decydując się na reprezentację obiektu domeny, powinieneś pomyśleć o procesie, dla którego służy on jako świadek. Jeśli
FirstName
naprawdę powinien być niepustym ciągiem, powinien stać jako świadek, że system uruchomił jakiś proces, który zapewnia, że co najmniej jeden znak spacji został utworzony w sekwencji znaków, które zostały ci przekazane.Podobnie
Age
obiekt prawdopodobnie powinien być świadkiem, że jakiś proces obliczył różnicę między dwiema datami. Odints
ogół nie są wynikiem obliczenia różnicy między dwiema datami, są ipso faktycznie niewystarczające do przedstawienia wieku.Jest więc oczywiste, że prawie zawsze chcesz utworzyć klasę (która jest tylko programem) dla każdego obiektu domeny. Więc w czym problem? Problem polega na tym, że C # (co kocham) i Java wymagają zbyt wiele ceremonii w tworzeniu klas. Możemy jednak wyobrazić sobie alternatywne składnie, które znacznie ułatwiłyby definiowanie obiektów domeny:
Na przykład F # ma w tym celu ładną składnię w postaci aktywnych wzorców :
Chodzi o to, że tylko dlatego, że język programowania sprawia, że kłopotliwe jest definiowanie obiektów domeny, nie oznacza, że tak naprawdę jest to zły pomysł.
† Nazywamy te rzeczy również warunkami pocztowymi , ale wolę termin „świadek”, ponieważ służy on jako dowód na to, że jakiś proces może i był realizowany.
źródło
Pomyśl o tym, co zyskujesz, stosując klasę vs. prymityw. Jeśli korzystasz z klasy, możesz cię zdobyć
PhoneNumber
klasą, nie powinno być dla mnie możliwe przypisanie go do łańcucha lub łańcucha losowego (tj. „ogórka”), gdzie oczekuję numeru telefonu)a te korzyści ułatwiają życie, poprawiają jakość kodu, czytelność i przeważają nad bólem związanym z używaniem takich rzeczy, rób to. W przeciwnym razie trzymaj się prymitywów. Jeśli jest blisko, zadzwoń do sądu.
Zrozum także, że czasami dobre nazewnictwo może po prostu sobie z tym poradzić (tj. Ciąg nazwany
firstName
kontra tworzenie całej klasy, która po prostu otacza właściwość łańcucha). Zajęcia nie są jedynym sposobem na rozwiązanie problemów.źródło