Lekcja 1.2: Wstrzykiwanie zależności

Wstrzykiwanie zależności

 

W tym przykładzie spróbujemy poprawić nasz dotychczasowy kod, bazując na regule wstrzykiwania zależności. Załóżmy, że otrzymaliśmy konkretne zadanie, mamy podmienić sposób generowania identyfikatorów dla płatności. Chcemy zmienić algorytm, który aktualnie jest używany do tego, żeby generować te identyfikatory. Zobaczmy do serwisu PaymentService. Widzimy, że w tej chwili ten aktualny generator zaimplementowany wcześniej jest stworzony na poziomie klasy PaymentService jako po prostu kolejne pole, jako kolejny element instancyjny i teraz to wygląda bardzo niewinnie, natomiast jest bardzo kłopotliwe dlatego, że jeżeli napiszemy, czy stworzymy kolejną wersję takiego generatora to trzeba będzie modyfikować FakePaymentService, trzeba będzie ingerować w kod, który tutaj się znajduje. Druga kwestia, gdybyśmy chcieli napisać testy jednostkowe do naszego serwisu, co zrobimy w późniejszym czasie, to będzie kłopot z postawieniem tu implementacji testowej czy jakiegoś mocka. Zwróćcie uwagę, że wprowadziliśmy dosyć mocne sprzężenie pomiędzy tą naszą usługą, która służy do tego, żeby realizować płatności a z drugiej strony generatorem, który jest odpowiedzialny za to, żeby dostarczyć unikalne identyfikatory. To jest bardzo prosty przykład, ale już na nim widać, że to zarządzanie samodzielne zależnościami, czyli tworzenie i konfigurowanie takich zależności na poziomie komponentu, na poziomie klasy jest nie najlepszym pomysłem, dlatego że to rodzi wiele problemów jakby się nad tym bardziej zastanowić. To również jest złamanie zasady pojedynczej odpowiedzialności, bo nasza usługa do płatności powinna realizować płatności, a nie konfigurować czy tworzyć generator do tychże płatności. Co możemy zrobić. Możemy, spróbować rozluźnić to powiązanie wprowadzając po pierwsze interfejs, a po drugie konfigurując taki generator z zewnątrz. Zacznijmy od tej pierwszej części. Możemy dzięki temu, że dzisiaj mamy już bardzo wyspecjalizowane IDE zrobić to niemal bezboleśnie, automatycznie klikając prawym klawiszem myszki i wybierając Refactor i Extract interface. Możemy w łatwy sposób wyodrębnić interfejs na podstawie metod, które znajdują się w tej klasie generatora. Nazwijmy ten interfejs PaymentIdGenerator. To będzie taka bardzo ogólna nazwa, definiują kontrakt, a niemówiąca przy okazji nic o konkretnej implementacji. Jednocześnie zaznaczmy metodę getNext tak, żeby ona była ujęta w ramach tego interfejsu. Jeżeli teraz wybierzemy opcję refactor i zatwierdzimy te zmiany, to zwróćcie uwagę, że na poziomie generatora mamy informację o tym, że implementujemy aktualnie PaymentIdGenerator. Gdybyśmy zajrzeli do naszej usługi to w tej chwili pole, które zostało zdefiniowane na poziomie klasy jest typu PaymentIdGenerator, czyli znowu używamy abstrakcji, używamy interfejsu i to jest połowa sukcesu. To jest jakby połowa tej drogi do poprawienia jakości tego naszego rozwiązania. Druga część tak jak powiedzieliśmy, to jest trzymanie się tej zasady, że wszystkie zależności są konfigurowane i podawane z zewnątrz. Co możemy zrobić? Takim naturalnym sposobem będzie tu zastosowanie konstruktora. Jeżeli byśmy mieli konstruktor, który przyjmuje PaymentIdGenerator, to moglibyśmy z zewnątrz, właśnie w oparciu o wygenerowany przed chwilą interfejs podawać różne implementacje takiego generatora i nie zmieniać niczego na poziomie naszej usługi PaymentService. Jednocześnie zwróćmy uwagę, jak łatwe będzie teraz testowanie. Będzie można tu w prosty sposób podstawić na przykład jakiegoś mocka, jakąś wersję testową takiego generatora. Po raz kolejny możemy tu poprosić Lomboka o pomoc. Dodając adnotację @RequiredArgsConstructor powodujemy, że Lombok wygeneruje konstruktor, który będzie zawierał jako parametry wejściowe wszystkie pola, które będą oznaczone albo przez adnotację @NonNull, my takiego wariantu nie mamy, albo będą właśnie polami finalnymi. Na poziomie naszej klasy, oczywiście niestatycznymi. W związku z tym dysponujemy już teraz konstruktorem, który przyjmuje PaymentIdGenerator. To oczywiście spowoduje błąd kompilacji na poziomie klasy Application. Jeżeli wrócimy do tej naszej głównej klasy widać ewidentnie, że teraz kompilator oczekuje tego, że podamy jakąś implementację naszego generatora. Spróbujmy ten kod naprawić. Nasz kod ponownie się kompiluje. Spróbujmy go uruchomić. Okazuje się, że efekt jest dokładnie taki sam. Mamy zarówno informację o tym, że płatność została zrealizowana, czyli zalogowaliśmy to na poziomie naszej usługi, ale z drugiej strony mamy też Payment, który został wyrzucony na konsolę, został zalogowany. No to teraz zobaczmy, jak łatwo jest podmienić taką implementację. Jak łatwo jesteśmy w stanie teraz podstawić inny wariant takiego generatora. Ja specjalnie zaimplementuje w tej chwili taki wariant, który jest bardzo, bardzo prosty. Załóżmy, że nasz nowy generator będzie tworzył identyfikatory inkrementując po prostu jakąś wartość całkowitą i uzupełniając ją np. do dziesięciu znaków zerami. Czyli stworzymy teraz nową klasę, którą nazwiemy IncrementalPaymentIdGenerator. Ta klasa oczywiście powinna implementować interfejs, który wcześniej stworzyliśmy, czyli PaymentIdGenerator. To z kolei wymusi istnienie metody getNext, a nam pozostaje tylko zaimplementowanie tego mechanizmu, o którym wcześniej mówiłem, czyli przygotujemy najpierw pole, które będzie znowu trzymało format, szablon takiego id. Nazwiemy je ID_FORMAT i w naszym przypadku to będzie %010d. Czyli mówiąc krótko, będziemy tu podstawiali wartość liczbową i uzupełniali do dziesięciu znaków zerami. Teraz pozostaje nam jeszcze dodać pole indeksu, który będziemy inkrementować. Taki indeks dobrze by było też mieć możliwość zmieniać. I na koniec pozostaje nam jeszcze zwrócić sformatowany identyfikator. I podbić, zinkrementować wartość naszego licznika. Nasza implementacja jest gotowa. Pozostaje ją teraz przetestować, podstawić na poziomie Application. Jeżeli teraz wrócimy do tego kodu w metodzie main, wystarczy, że zmienimy teraz konkretną klasę implementującą interfejs generatora. Zwróćcie uwagę, że ta zmiana nie została rozpropagowana, ona nie wymusiła na nas kolejnych zmian na poziomie serwisu czy na poziomie innych klas. Można powiedzieć, że ten kod, który mamy na początku metody to jest taki kod konfiguracyjny i pozwala on nam na dowolny wybór implementacji, którą będziemy stosowali. Ten mechanizm, który pokazaliśmy, można oczywiście stosować wielokrotnie, na wielu poziomach, co pozwala na to, żeby w takiej dużej aplikacji wymieniać dosłownie całe fragmenty, moduły bez konieczności większych zmian na poziomie kodu. Spróbujmy uruchomić nasze rozwiązanie. I okazuje się, że ona nadal działa. Ewidentnie widać, że korzystamy już z innego generatora mamy zupełnie inny identyfikator, zupełnie inny format tego identyfikatora. Natomiast tak jak powiedzieliśmy mamy teraz swobodę pod kątem wymiany implementacji, ale także pod kątem testowania

Zaloguj się
Rejestracja jest darmowa!

Administratorem danych jest Sages Sp. z o.o. z siedzibą w Warszawie przy ul. Nowogrodzkiej 62c. Podanie danych jest dobrowolne. Osobie, której dane dotyczą przysługuje prawo wglądu do danych osobowych, ich zmiany oraz usunięcia w sposób określony w Polityce prywatności.

Please accept the Terms and Conditions to proceed.