Palvelu vs. tehdas – kerta kaikkiaan

Mitä? Taas yksi artikkeli, joka vastaa suureen kysymykseen: Service vs Factory, mitä minun pitäisi käyttää? Kyllä, näyttää siltä, että tätä ei enää tarvita, koska internetissä on tonneittain resursseja, jotka käsittelevät tätä aihetta. Kävi ilmi, että tämä kysymys putkahtaa edelleen esiin noin viikon välein eri kanavissa, ja vaikka olisi lukenut StackOverflow’n kymmenen tärkeintä vastausta, asia ei ole vieläkään kovin selvä. Siitä huolimatta näyttää myös siltä, että nykyiset resurssit verkossa eivät todellakaan edistä todellista parasta käytäntöä, varsinkin jos otamme huomioon verkkoalustan viimeaikaiset liikkeet. ES6 I’m looking at you!

Tämä artikkeli selittää lopullisesti palveluiden ja tehtaiden välisen eron ja sen, miksi haluamme suosia palveluita tehtaiden sijaan.

Palveluiden ja tehtaiden välinen ero

Okei, mitä eroa on palvelun ja tehtaan välillä AngularJS:ssä? Kuten kaikki tiedämme, voimme määritellä palvelun näin:

app.service('MyService', function () { this.sayHello = function () { console.log('hello'); };});

.service() on moduulimme metodi, joka ottaa nimen ja funktion, joka määrittelee palvelun. Aika suoraviivaista. Kun olemme määritelleet, voimme injektoida ja käyttää kyseistä tiettyä palvelua muissa komponenteissa, kuten ohjaimissa, direktiiveissä ja suodattimissa, näin:

app.controller('AppController', function (MyService) { MyService.sayHello(); // logs 'hello'});

Okei, selvä. Nyt sama asia kuin tehtaalla:

app.factory('MyService', function () { return { sayHello: function () { console.log('hello'); } }});

Jälleen, .factory() on metodi moduulissamme ja se ottaa myös nimen ja funktion, joka määrittelee tehtaan. Voimme injektoida ja käyttää sitä täsmälleen samalla tavalla kuin palvelua. Mitä eroa tässä nyt on?

Noh, saatat huomata, että sen sijaan, että työskentelisimme this:n kanssa tehtaassa, palautamme objektiliteraalin. Miksi näin on? Kävi ilmi, että palvelu on konstruktorifunktio, kun taas tehdas ei ole. Jossain syvällä Angular-maailman sisällä on koodi, joka kutsuu Object.create() palvelun konstruktorifunktiolla, kun se instantioidaan. Tehdasfunktio on kuitenkin oikeastaan vain funktio, jota kutsutaan, minkä vuoksi meidän on palautettava eksplisiittisesti objekti.

Jotta tämä olisi hieman selkeämpää, voimme yksinkertaisesti katsoa Angularin lähdekoodia. Tältä näyttää factory()-funktio:

function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn });}

Se ottaa nimen ja välitetyn tehdasfunktion ja palauttaa periaatteessa samannimisen tarjoajan, jolla on $get-metodi, joka on meidän tehdasfunktiomme. Mitä tämä palveluntarjoajajuttu sitten on? No, aina kun pyydät injektorilta tiettyä riippuvuutta, se periaatteessa pyytää vastaavalta palveluntarjoajalta kyseisen palvelun instanssin kutsumalla $get()-metodia. Siksi $get() tarvitaan, kun luodaan palveluntarjoajia.

Muilla sanoilla, jos injektoimme MyService jonnekin, mitä tapahtuu kulissien takana on:

MyServiceProvider.$get(); // return the instance of the service

Oikein, tehdasfunktioita vain kutsutaan, entä palvelukoodi? Tässä on toinen pätkä:

function service(name, constructor) { return factory(name, );}

Katsokaa, käy ilmi, että kun kutsumme service(), se itse asiassa kutsuu factory(). Mutta se ei vain välitä palvelukonstruktorifunktiotamme tehtaalle sellaisenaan. Se välittää funktion, joka pyytää injektoria instantioimaan ja objektin annetulla konstruktorilla. Toisin sanoen: palvelu kutsuu ennalta määriteltyä tehdasta, joka päätyy vastaavan palveluntarjoajan $get()-metodiksi. $injector.instantiate() on metodi, joka lopulta kutsuu Object.create() konstruktorifunktiolla. Siksi käytämme this:tä palveluissa.

Okei, käy siis ilmi, että riippumatta siitä, mitä käytämme, service() tai factory(), kutsutaan aina tehdasta, joka luo palvelumme palveluntarjoajan. Tästä pääsemmekin Angularin historian useimmin kysyttyyn kysymykseen:

Kumpaa pitäisi käyttää?

Tämän kysymyksen kysyminen internetissä vie meidät pariin artikkeliin ja StackOverflow-vastauksiin. Ensimmäinen on tämä vastaus. Siinä sanotaan:

”Periaatteessa palvelun ja tehtaan ero on seuraava:”

app.service('myService', function() { // service is just a constructor function // that will be called with 'new' this.sayHello = function(name) { return "Hi " + name + "!"; };});app.factory('myFactory', function() { // factory returns an object // you can run some code before return { sayHello : function(name) { return "Hi " + name + "!"; } }});

Me nyt jo tiedämme, mitä kulissien takana tapahtuu, mutta tämä vastaus lisää vielä yhden kommentin. Siinä sanotaan, että voimme ajaa koodia ennen kuin palautamme objektimme literaalin. Tämä periaatteessa antaa meille mahdollisuuden tehdä joitain konfigurointijuttuja tai luoda objekti ehdollisesti tai olla luomatta, mikä ei näytä olevan mahdollista luotaessa palvelua suoraan, minkä vuoksi useimmat resurssit suosittelevat käyttämään factoriesia palveluiden sijaan, mutta perusteluita ei voi arvostaa.

Entä jos sanoisin, että voimme tehdä täsmälleen saman asian myös palveluiden kanssa?”

Juu, oikein. Palvelu on konstruktorifunktio, mikä ei kuitenkaan estä meitä tekemästä lisätyötä ja palauttamasta objektilitteraaleja. Itse asiassa JavaScriptin konstruktorifunktiot voivat palauttaa mitä tahansa. Voimme siis ottaa palvelukoodimme ja kirjoittaa sen niin, että se tekee periaatteessa täsmälleen saman asian kuin tehtaamme:

app.service('MyService', function () { // we could do additional work here too return { sayHello: function () { console.log('hello'); }; }});

Hoppla, mitä nyt? Tajusimme juuri, että riippuen siitä, miten kirjoitamme palvelumme, näiden kahden välillä ei ole enää lainkaan eroa. Iso kysymys jää jäljelle:

Palveluiden avulla voimme käyttää ES6-luokkia

Tietysti palveluiden kirjoittaminen tuolla tavalla on tavallaan kontraproduktiivista, koska sitä kutsutaan konstruktorifunktiona, joten sitä pitäisi myös käyttää sellaisenaan. Onko siinä sitten yhtään mitään etua toiseen nähden? Kyllä on. Kävi ilmi, että on itse asiassa parempi käyttää palveluja aina kun se on mahdollista, kun on kyse ES6:een siirtymisestä. Syy tähän on yksinkertaisesti se, että palvelu on konstruktorifunktio ja tehdas ei ole. Kun työskentelemme ES5:ssä konstruktorifunktioiden kanssa, voimme helposti käyttää ES6-luokkia, kun siirrymme ES6:een.

Voidaan esimerkiksi ottaa koodimme ja kirjoittaa se uudelleen ES6:een näin:

class MyService { sayHello() { console.log('hello'); }}app.service('MyService', MyService);

Es6-luokka on oikeastaan vain konstruktorifunktio ES5:ssä. Kirjoitimme tästä tänään artikkelissa Using ES6 with Angular, jos et ole vielä lukenut tuota artikkelia, suosittelen tutustumaan siihen.

Tehtaiden kanssa tämä ei ole mahdollista, koska niitä kutsutaan yksinkertaisesti funktioina. Toivottavasti tämä artikkeli teki kaiken selväksi ja rohkaisee ihmisiä olemaan käyttämättä factoriesia palveluiden sijaan, jos he eivät tiedä mitä käyttää.

Tämän ja paljon muuta opit Angular Master Classissa!

Vastaa

Sähköpostiosoitettasi ei julkaista.