Value Object: Praktyczna koncepcja

Zastanawiasz się, jak tworzyć bezpieczniejsze i bardziej trwałe aplikacje? Poznaj Value Object - niemodyfikowalny obiekt w programowaniu, który pozwala reprezentować ważne wartości i ułatwia zarządzanie stanem aplikacji. Odkryj, jakie korzyści wynikają z jego wykorzystania!

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 🧸

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments
Włączyć powiadomienia? Bardzo pragnę Dam sobie radę