Prymitywy, takie jak string
lub int
, nie mają znaczenia w domenie biznesowej. Bezpośrednią konsekwencją tego jest to, że możesz błędnie użyć adresu URL, gdy oczekiwany jest identyfikator produktu, lub użyć ilości, gdy spodziewasz się ceny .
Z tego powodu wyzwanie Kalistenika obiektów obejmuje zawijanie prymitywów jako jedną z jego zasad:
Reguła 3: Zawiń wszystkie prymitywy i łańcuchy
W języku Java int jest prymitywnym, a nie prawdziwym obiektem, więc podlega innym regułom niż obiekty. Jest używany ze składnią, która nie jest zorientowana obiektowo. Co ważniejsze, sama int jest tylko skalarem, więc nie ma znaczenia. Gdy metoda przyjmuje int jako parametr, nazwa metody musi wykonać całą pracę polegającą na wyrażeniu intencji. Jeśli ta sama metoda zajmuje godzinę jako parametr, o wiele łatwiej jest zobaczyć, co się dzieje.
Ten sam dokument wyjaśnia, że istnieje dodatkowa korzyść:
Małe przedmioty, takie jak Godzina lub Pieniądze, również dają nam oczywiste miejsce do postawienia zachowania, które w innym przypadku byłoby zaśmiecone wokół innych klas .
Rzeczywiście, gdy stosowane są prymitywy, zazwyczaj niezwykle trudne jest śledzenie dokładnej lokalizacji kodu związanego z tymi typami, co często prowadzi do poważnego powielania kodu . Jeśli jest Price: Money
klasa, naturalne jest sprawdzenie zasięgu w środku. Jeżeli zamiast tego do przechowywania cen produktów stosuje się int
(gorzej, a double
), kto powinien potwierdzić zakres? Produkt? Rabat? Wózek?
Wreszcie trzecią korzyścią niewymienioną w dokumencie jest możliwość stosunkowo łatwej zmiany rodzaju bazowego. Jeśli dzisiaj mój ProductId
ma short
typ podstawowy, a później muszę go użyć int
, istnieje szansa, że kod do zmiany nie obejmie całej bazy kodu.
Wadą - i ten sam argument stosuje się do każdej reguły ćwiczenia Calisthenics Object - jest to, że jeśli szybko staje się zbyt przytłaczający, aby stworzyć klasę dla wszystkiego . Jeśli Product
zawiera, ProductPrice
które dziedziczy, z PositivePrice
którego dziedziczy, z Price
którego dziedziczy Money
, to nie jest to czysta architektura, ale raczej kompletny bałagan, w którym aby znaleźć jedną rzecz, opiekun powinien otwierać kilkadziesiąt plików za każdym razem.
Inną kwestią do rozważenia jest koszt (pod względem linii kodu) tworzenia dodatkowych klas. Jeśli opakowania są niezmienne (jak zwykle powinny), oznacza to, że jeśli weźmiemy C #, musisz mieć w opakowaniu co najmniej:
- Właściciel nieruchomości,
- Jego zaplecze,
- Konstruktor, który przypisuje wartość do pola bazowego,
- Zwyczaj
ToString()
,
- Komentarze do dokumentacji XML (która tworzy wiele wierszy),
- A
Equals
i GetHashCode
zastępuje (również dużo LOC).
i ewentualnie, w stosownych przypadkach:
- DebuggerDisplay atrybut,
- Override
==
i !=
operatorów,
- W końcu przeciążenie niejawnego operatora konwersji do płynnej konwersji do iz typu enkapsulowanego,
- Kontrakty kodowe (w tym niezmiennik, który jest dość długą metodą z trzema atrybutami),
- Kilka konwerterów, które będą używane podczas serializacji XML, serializacji JSON lub przechowywania / ładowania wartości do / z bazy danych.
Sto LOC na proste opakowanie sprawia, że jest on zbyt wygórowany, dlatego też możesz być całkowicie pewien długoterminowej rentowności takiego opakowania. Pojęcie zakresu wyjaśnione przez Thomasa Junk jest tutaj szczególnie istotne. Pisanie setek LOC-ów, które będą reprezentować ProductId
używane w całej bazie kodu, wygląda całkiem użytecznie. Napisanie klasy tego rozmiaru dla fragmentu kodu, który tworzy trzy wiersze w ramach jednej metody, jest znacznie bardziej wątpliwe.
Wniosek:
Zawijaj operacje podstawowe w klasach, które mają znaczenie w domenie biznesowej aplikacji, gdy (1) pomaga zmniejszyć liczbę błędów, (2) zmniejsza ryzyko duplikacji kodu lub (3) pomaga później zmienić typ bazowy.
Nie zawijaj automatycznie wszystkich prymitywów, które znajdziesz w kodzie: w wielu przypadkach użycie string
lub int
jest całkowicie w porządku.
W praktyce public string CreateNewThing()
zwracanie instancji ThingId
klasy zamiast string
może pomóc, ale możesz również:
Zwraca instancję Id<string>
klasy, czyli obiekt typu ogólnego wskazujący, że typem bazowym jest łańcuch. Zaletą czytelności jest brak konieczności utrzymywania wielu typów.
Zwraca instancję Thing
klasy. Jeśli użytkownik potrzebuje tylko identyfikatora, można to łatwo zrobić za pomocą:
var thing = this.CreateNewThing();
var id = thing.Id;