Refaktoryzacja z wykorzystaniem Value Objects
Jest to kolejna świetna opcja jaką mamy, kiedy naszym celem jest refaktoryzacja wielkich klas. W tym przypadku wyabstrahowanie tzw. Value Objects .
Na czym to polega?
Polega to na wyniesieniu jakiejś konkretnej wartości typu prymitywnego do jej własnej klasy, która będzie przechowywała całe zachowanie z nią związane .
Wyobraź sobie, że masz kilka metod które służą jako gettery, dla przykładu: revenue(), revenueInDollars(), revenueAsCurrency()... Napisałem tylko trzy. A potencjalnie będzie ich dużo więcej. A klasa ciągle rośnie. I tutaj właśnie jedną z opcji do których możesz się uciec, jest wyodrębnienie value object.
Spójrz na nazwy tych metod, każda z nich zaczyna się od słowa "revenue". I fakt, że ciągle powtarza się to jedno słowo, może być (ale nie musi) oznaką tego, że coś ci tu umyka. Jakiś rodzaj abstrakcji, który z całą pewnością w tym przypadku by był mile widziany .
Jak tego dokonać?
Oto co możesz zrobić w tym przypadku. Utwórz nową klasę o nazwie "Revenue". To będzie nasz value object.
Po przeniesieniu do nich wcześniejszych metod powinieneś zauważyć, jak prefix "revenue" w nazwie tych metod stał się kompletnie redundantny i bezużyteczny. To dobry znak . Jesteśmy na właściwej drodze.
Odtąd będziesz operować na value object, zamiast na suchej wartość . Cała dedykowana klasa, która enkapsuluje wszelakie zachowanie i własności tej wartości .
Kiedy stosować?
Powinieneś stosować tę technikę tylko dla ważnych wartości należących do twojej domeny, konceptu, idei. Wtedy, kiedy zmiana tych wartości na value objects naprawdę poprawi coś w twojej pracy, w twoim projekcie . Dla przykładu: aplikacja do ćwiczeń - value object masy ciała.
Warto to rozważyć, jeśli jakaś konkretna wartość ma (lub będzie miała) dodatkowe zachowanie z nią związane. Jak w poprzednim przykładzie - konwersja na wartość w dolarach czy formatowanie jako waluta. Jeśli więc jest w tym jakieś dodatkowe zachowanie - warto zastanowić się nad przeniesieniem tego do dedykowanej klasy, która będzie właśnie value objectem.
Nie w każdym przypadku stosowanie tej techniki ma sens. I warto o tym wspomnieć, ponieważ wiele osób ma w zwyczaju nadużywanie tej techniki. Poznają ją i nagle wszędzie ją stosują, nagle każda rzecz staje się value objectem. A jest to głupie, ponieważ są przydatne tylko kiedy to naprawdę ma sens. Ale z całą pewnością nie powinniśmy jej stosować domyślnie, od razu, dla wszystkiego. Skazujesz się wtedy tylko na większą ilość zbędnej pracy.
Niemutowalność
Value Object powinien być niemutowalny. Co to oznacza? Oznacza to tyle, że nie powinieneś być w stanie zmienić istniejącego value object. Nie powinno być możliwości nadpisania jego wartości. A co kiedy musisz zmienić tę wartość? Nie zmieniasz istniejącej, tylko tworzysz i zwracasz nową (nowa instancja value object).
Eloquent i Accessors
Warto wspomnieć o ciekawej możliwości jaką daje nam Eloquent - a którą możemy tutaj fajnie wykorzystać .
W Eloquencie możemy zdefiniować metodę dostępową (tzw. accessor). Metoda ta zostanie automatycznie wywołana kiedy odwołasz się do wskazanego przez ciebie w nazwie tej metody atrybutu modelu. Przykład: "getFirstNameAttribute()" - nazwa metody będąca accessorem atrybutu first_name.
I kiedy odwołasz się do tego atrybutu, zwróci ci dokładnie to, co zwraca zdefiniowana przez ciebie metoda .
I możesz to wykorzystać, do zwrócenia właśnie twojego value object, do którego w metodzie tej przekażesz ten atrybut modelu (tę wartość) . Przykład:
public function getRevenueAttribute($revenue)
{
return new Revenue($revenue);
}
Wówczas przy odwoływaniu się do tego atrybutu zamiast dostawać czystą wartość, to dostaniesz obiekt klasy Revenue - czyli twój value object .
toString()
Warto również zaimplementować w tej klasie metodę toString(). Dzięki której będziesz mógł zachować funkcjonalność odwoływania się do tej wartości jak do stringa .
Możesz również sam zdecydować jak powinna wyglądać domyślna reprezentacja tej wartości przy odwoływaniu się do niej jak do stringa. Bardzo fajna możliwość.
Kod klasy z omawianego przykładu
<?php
namespace App;
class Revenue
{
private $revenue;
public function __construct($revenue)
{
$this->revenue = $revenue;
}
public function inDollars()
{
return $this->revenue / 100; // 86
}
public function asCurrency()
{
return money_format('$%i', $this->inDollars()); // $86.00
}
public function __toString()
{
return (string) $this->AsCurrency();
}
}
Zalety
Warto wspomnieć, że poprawie uległo również twoje API.
A nie tylko to, bo również kod został oczyszczony, twoja architektura uległa poprawie.
I ważny koncept w twojej domenie z którym związane było niestandardowe zachowanie zyskał własną, dedykowaną klasę .
Podsumowanie
Kolejna świetna technika którą nie raz pewnie zastosujesz . Niesie ze sobą sporo dobrego. Jednak pamiętaj, używaj jej tylko wtedy, kiedy ma to sens. Nie rób nagle na siłę ze wszystkiego value objects.
Kończąc
Dziękuję ci za lekturę i proszę cię o zostawienie komentarza, podzielenie się nim z innymi oraz zerknięcia na inne moje artykuły . A nie jest to jedyny artykuł z serii o refaktoryzacji.
A tymczasem życzę ci dobrego dnia, bywaj !