Serwis vs Fabryka (Service vs Factory)

W tym rozdziale zajmujemy się tematem, który dla każdego kto styka się z AngularJS po raz pierwszy, spędza sen z powiek. Mowa tutaj o usługach. W AngularJS mamy kilka typów usług, które pozwalają nam zająć się różnymi kategoriami zagadnień w ramach szeroko pojętej definicji tego słowa. W tym rozdziale skupimy się na tych usługach, które są szczególnie pomocne w kontekście budowy warstwy przetwarzania danych:
  • Serwis (Service)
  • Fabryka (Factory)
Istotną cechą obiektów usług jest to, że są one tworzone raz i tylko raz, aż do momentu ponowego przeładowania strony. Niezależnie od tego ile wykonamy przełączeń między widokami, to o ile fizycznie pozostaniemy na jednej stronie, to dane przechowywane w usługach będą ciągle pamiętane.

Serwis (Service)

Zarówno serwis (service) jak i fabryka (factory) mogą nam posłużyć do budowy frontendowej warstwy przetwarzania danych. Za ich pomocą będziemy wysyłać żądania HTTP, przyjmować odpowiedzi do tych żądań oraz wykonywać algorytmy operujące na danych dostarczanych z widoków przez kontrolery. Dzięki możliwości wstrzykiwania wykorzystamy tutaj również serwisy wbudowane w AngularJS, takie jak na przykład serwis $http.

Serwis i fabryka są ze sobą mocno powiązane. Zagłębiając się w kod AngularJS zauważymy, że podczas uruchamiania, funkcja serwisu za każdym razem woła funkcję fabryki . Tworząc serwis i podając funkcję konstruującą powinniśmy mieć zatem świadomość, że zostanie ona wykorzystana przez fabrykę w celu stworzenia obiektu:
function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
        return $injector.instantiate(constructor);
    }]);
} 
Tak to wygląda gdy zajrzymy do kodu frameworka. Co jednak z naszym kodem? Jak stworzyć pierwszy serwis? W tym celu musimy wykorzystać funkcję service, która zarejestruje naszą usługę w module:
angular.module(<NAZWA_MODUŁU>).service(<NAZWA_SERWISU>, [<WSTRZYKIWANE_ZALEŻNOŚCI>, 
function(<WSTRZYKIWANE_ZALEŻNOŚCI>) {...}])
Jako pierwszy parametr - oznaczający nazwę serwisu - przyjmiemy AppaFormService . Drugim parametrem funkcji service będzie funkcja konstruująca, której wywołanie doprowadzi do stworzenia obiektu serwisu. Tak jak pisaliśmy wcześniej, samym uruchomieniem funkcji konstruującej zajmie się fabryka.
angular.module('appa.form').service('AppaFormService',
        [ '$http', '$q', '$log', 'Upload', function($http, $q, $log, Upload) {

            var dataCache = {
                appaCategories : null,
                appaTypes : null,
                appaAttributes : null,
                ...
            };
            
            this.getAppaTypes = function() {
                dataCache.appaTypes = ...
            }
            
            this.getAppaCategories = function() {
                dataCache.appaCategories = ...
            }
            
            this.getAppaAttributes = function() {
                dataCache.appaAttributes = ...
            }           

            ...
        } ]);
W tym miejscu pojawia się pytanie. Skoro wywołanie serwisu jest redundantne, to czy nie lepiej jest ominąć ten krok i wywołać fabrykę bezpośrednio? Oczywiście możemy tak robić i jest to nawet preferowane przez nas podejście, natomiast wydaje się, że to czy wybierzemy serwis czy fabrykę jest tak naprawdę sprawą drugorzędną. Wybór taki może być podyktowany tym, że po prostu w którymś formacie zapisu czujemy się trochę lepiej. Na zasadzie co kto lubi.

Fabryka (Factory)

Fabryka to typ usługi, która albo jest uruchamiana przez service (co pokazaliśmy wyżej), albo może być też wywołana bezpośrednio przez nas samych. Wtedy tworzymy ją - podobnie jak service - korzystając z odpowiedniej funkcji dostępnej w ramach modułu. Używamy do tego funkcji o nazwie factory :
angular.module(<NAZWA_MODUŁU>).factory(<NAZWA_FABRYKI>, [<WSTRZYKIWANE_ZALEŻNOŚCI>, 
function(<WSTRZYKIWANE_ZALEŻNOŚCI>) {...}])
Charakterystyczną cechą fabryki odróżniającą ją od serwisu jest to, że funkcja użyta do jej tworzenia nie jest funkcją konstruującą. Jest to regularna funkcja Javascript, w której możemy tworzyć inne funkcje realizujące algorytmy związane z przetwarzaniem danych.

Najważniejsze jednak jest to, że musimy pamiętać o zwróceniu rezultatu działania tej funkcji w postaci obiektu. Jest to dokładnie taki obiekt jaki byłby tutaj stworzony gdyby fabryka była uruchamiana z poziomu serwisu, tyle że teraz tworzymy go literalnie (zamiast jawnego wywołania funkcji konstruującej).
angular.module('appa.form').factory('AppaFormFactory',
        [ '$http', '$q', '$log', 'Upload', function($http, $q, $log, Upload) {
        	
            var dataCache = {
                appaCategories : null,
                appaTypes : null,
                appaAttributes : null,
                ...
            };                    
            
            function getAppaTypes() {
            
            	dataCache.appaTypes = ...
            }
            
            function getAppaCategories() {
            
            	dataCache.appaCategories = ...
            }
            
            function getAppaAttributes() {
            
            	dataCache.appaAttributes = ...
            }
        	
            ...
            
            return {
                getAppaTypes : getAppaTypes,
                getAppaCategories : getAppaCategories,
                getAppaAttributes : getAppaAttributes,
                ...
            };
            
        } ]);
Tak więc ostatecznie dochodzimy do wniosku, że obiekt stworzony wcześniej przez funkcję konstruującą w serwisie i przekazaną do fabryki:
factory('AppaFormService', ['$injector', function($injector) {
    return $injector.instantiate(constructor);
}]);   
jest równoważny w stosunku do obiektu tworzonego przez nas literalnie bezpośrednio w samej fabryce:
factory('AppaFormFactory', [ ..., function(...) {
    	
    ...
    
    return {
        getAppaTypes : getAppaTypes,
        getAppaCategories : getAppaCategories,
        getAppaAttributes : getAppaAttributes,
        ...
    };
    
} ]);

Podsumowanie

Opisywany w tym rozdziale temat nie jest łatwy do zrozumienia i z pewnością może nieco zniechęcać do głębszej analizy. Nie zmienia to jednak faktu, że po kilkukrotnym przeczytaniu naszych rozważań, a być może też skorzystaniu z innych źródeł (podanych na dole strony) stanie się on znacznie bardziej czytelny. Warto go przeanalizować, aby zrozumieć "pudełkową" konstrukcję usług serwis, fabryka i dostawca (service, factory, provider). O tej ostatniej nie wspomnieliśmy w tym rozdziale, gdyż różni się ona nieco w kontekście użycia od obu powyższych i dlatego poświęciliśmy jej osobny (kolejny) rozdział.
Rekomendacja
Od samego początku, gdy tylko AngularJS zaczął zyskiwać na popularności, programiści sprzeczali się co do tego, które z powyższych rozwiązań jest lepsze i co tak naprawdę powinno wykonywać naszą usługę. Patrząc na nazewnictwo bardziej naturalne wydaje się użycie rozwiązania typu service, niemniej factory też ma swoje niewątpliwe zalety, jak choćby możliwość wykonania kodu jeszcze przed utworzeniem obiektu usługi (we wspomnianej regularnej funkcji Javascript).

Warto mieć na uwadze, że jeśli w przyszłości będziemy chcieli migrować z ES5 na ES6, to wówczas będziemy tworzyć klasy nazywane już faktycznie serwisami i może warto od razu przyzwyczajać się do takiego nazewnictwa. Chociaż i tutaj można spojrzeć na sprawę z drugiej strony. Z czysto pragmatycznego punktu widzenia wysiłek programistyczny przenoszenia kodu z factory czy z service będzie porównywalny. Wybór pozostawiamy Wam.
Używamy w StartAPPa


W aplikacji do budowy warstwy usług wykorzystujemy factory. Za każdym razem gdy kontroler chce pobrać lub wysłać dane odwołuje się do obiektu usługi, która posiada wstrzyknięte odpowiednie mechanizmy do wykonywania zadań. W bieżącym rozdziale przedstawiliśmy fragment usługi działającej w ramach modułu Formularz Zaawansowany .

Pełna wersja kodu tej usługi jest odpowiedzialna za pobieranie z serwera i przekazanie do kontrolera danych początkowych oraz umożliwia zapis wszystkich danych formularza. Dodatkowo dane słownikowe zapamiętywanie są w cache'u, aby uniknąć regularnego pobierania ich przy każdej próbie wywołania funkcji pobierającej.
Linki:
https://docs.angularjs.org/guide/providers#factory-recipe
https://docs.angularjs.org/guide/providers#service-recipe
https://www.quora.com/What-is-the-difference-between-a-factory-and-service-in-AngularJS-and-when-should-each-be-used

Stale się rozwijamy, a więc bądź na bieżąco!
Na ten adres będziemy przesyłać informacje o ważniejszych aktualizacjach, a także o nowych materiałach pojawiających się na stronie.
Polub nas na Facebooku:
Nasi partnerzy: stackshare
Javappa to również profesjonalne usługi programistyczne oparte o technologie JAVA. Jeśli chesz nawiązać z nami kontakt w celu uzyskania doradztwa bądź stworzenia aplikacji webowej powinieneś poznać nasze doświadczenia.
Kliknij O nas .


Pozycjonowanie stron: Grupa TENSE