Czym jest Value Object?
Jak sama nazwa sugeruje, Value Object jest obiektem, który przechowuje wartość i już, to jest całe skrótowe wyjaśnienie. 😄
Ok, co do zasady, to pasuje jeszcze wyjaśnić mniej skrótowo tą koncepcję. Value Object cechuje się tym, że:
- reprezentuje pewną wartość lub zestaw wartości
- reprezentuje pojedyncze pojęcia np. waga, adres zamieszkania (ulica + miasto + kod pocztowy + …), pieniądz (wartość + waluta) itd.
- jego stan nigdy nie może zostać zmieniony => zamiast zmieniać to trzeba utworzyć nowy obiekt z nową wartością – jest to swego rodzaju zabezpieczenie, kiedy do tego samego obiektu mamy dostęp z dwóch (lub więcej) różnych referencji
- jest bez tożsamości – dwa Value Object są równe tylko wtedy kiedy ich wartości są ze sobą równe
Na co mi potrzebny Value Object?
Poza suchą teorią, ja sam nie raz wykorzystuję w swojej pracy Value Object. Dzięki swojej prostocie i łatwiej modyfikacji, można wiele osiągnąć.
Oczywiście, aby w ogóle było łatwiej zrozumieć o co mi chodzi to posłużę się przykładem: Tworzymy aplikację, która ma za zadanie gromadzić informacje o naszym portfelu albo skarbonce i wyświetlać ile to my już mamy odłożonych pieniędzy. Ot takie proste pokazywanie „hajsików” 😉. Tworzymy obiekt pt. „Portfel”, a w nim pole „ilość środków”.
class Wallet {
private _amount: number;
constructor(amount: number) {
this._amount = amount;
}
public get amount(): number {
return this._amount;
}
}
Wszystko pięknie, ale szybko zauważamy, że jednak to nam nie wystarcza. Przecież w naszym portfelu możemy mieć pieniądze o różnych walutach. Co wtedy?
Dobrze by było jakoś to ogarnąć. Dla uproszczenia załóżmy, że chcemy tylko wyświetlać pieniądze, ani przeliczać, ani łączyć. Utwórzmy zatem mapę, gdzie indeksami będą waluty, a wartościami środki w danej walucie:
class Wallet {
private _currencies: Map<string, number>;
constructor(currencies: Map<string, number>) {
this._currencies = currencies;
}
public get currencies(): Map<string, number> {
return this._currencies;
}
public set currencies(value: Map<string, number>) {
this._currencies = value;
}
}
Na tym etapie zaczyna nam się robić pewien bałagan. Nie mamy pewności, że tak podana mapa, zawsze będzie prawidłowa oraz to, że wartości będą odpowiednie. Tak w sumie to tej pewności w ogóle nie było od początku.
Na ratunek przychodzi Value Object! 😃
Zacznijmy od skonstruowania klasy „Pieniądze”, dzięki której będziemy mogli tworzyć obiekty z wartościami. Każdy nasz „pieniądz” będzie składał się z wartości „ilość” oraz jednostki „waluty”. Dodatkowo zróbmy też logikę kodu, dzięki której będziemy mogli dodawać pieniądze do naszego portfela. Nasza klasa będzie wyglądała tak:
class Money {
constructor(private _amount: number, private _currency: string) {}
public get amount(): number {
return this._amount;
}
public get currency(): string {
return this._currency;
}
public add(money: Money): Money {
if (money.currency !== this._currency) {
throw new Error('Cannot add money in different currencies');
}
return new Money(this._amount + money.amount, this._currency);
}
}
Najłatwiej to zrozumieć w taki sposób, że każdy nasz pieniądz przechowujemy w jakieś ilości i w jakiejś walucie.
Ok: czas to zintegrować z naszym portfelem:
class Wallet {
private _moneyList: Array<Money>;
constructor(moneyList: Array<Money>) {
this._moneyList = moneyList;
}
public get moneyList(): Array<Money> {
return this._moneyList;
}
public addMoney(money: Money): void {
const currencyIndex = this._moneyList.findIndex((item) => item.currency === money.currency);
if (currencyIndex === -1) {
this._moneyList.push(money);
} else {
const existingMoney = this._moneyList[currencyIndex];
this._moneyList[currencyIndex] = existingMoney.add(money);
}
}
}
Dzięki takiej niewiele większej ilości kodu, mamy ustandaryzowane pieniądze i zawsze też wiemy z czego się składają. Nie musimy się też przejmować żadnymi mapami. Dodatkowo ilość potrzebnych argumentów do dodawania pieniędzy wynosi tylko jeden. Przy mapie byłby obowiązek podania poza wartością jeszcze waluty, a tak to tylko podajemy gotowy pieniądz i reszta sama pięknie się ogarnia.
Do naszych pieniędzy możemy teraz dodać wiele przydatnych funkcjonalności bez konieczności modyfikacji portfela. Możemy uwarunkować to, które waluty są obsługiwane, możemy dodać przelicznik walut, a z tym zrobić sumę wszystkich pieniędzy pod jedną wybraną walutę, itd.
Zwięzłe wyjaśnienie Value Object
W przypadku projektowania i implementacji aplikacji, Value Object jest bardzo pomocny. Dzięki niemu możliwe jest lepsze rozdzielenie różnych elementów systemu, co skutkuje większą modularnością i łatwiejszym testowaniem kodu. W przykładzie kodu, Value Object pozwala na przechowywanie i manipulowanie danymi w obiektowej formie, a nie jak w przypadku zwykłych zmiennych. Dzięki temu, kod jest bardziej czytelny i zwięzły. Ponadto, korzystanie z Value Object ułatwia przeprowadzanie refaktoryzacji, co jest kluczowe dla zachowania wysokiej jakości kodu i uniknięcia błędów.
Podsumowanie
W dzisiejszym artykule dowiedzieliśmy się co to jest Value Object i dlaczego warto go stosować. Dajcie znać o tym czy stosujecie go w swoich projektach i do czego już się przydał.
Dziękuję bardzo za to, że jesteście ze mną. Jak coś to pytania proszę podsyłać w komentarzach, wtedy też innym będzie łatwiej uzupełnić wiedzę 😄
Do miłego następnego misiaczki kolorowe 🧸