Hyvänä JavaScript-kehittäjänä yrität kirjoittaa puhtaan, terveellisen ja ylläpidettävän koodin. Ratkaiset mielenkiintoisia haasteita, jotka ovat ainutlaatuisia, mutta eivät välttämättä vaadi ainutlaatuisia ratkaisuja. Olet todennäköisesti huomannut kirjoittavasi koodia, joka näyttää samanlaiselta kuin aiemmin käsittelemäsi täysin erilainen ongelma. Et ehkä tiedä sitä, mutta olet käyttänyt JavaScriptiä suunnittelumalli . Suunnittelumallit ovat uudelleenkäytettäviä ratkaisuja yleisiin ohjelmistosuunnittelun ongelmiin.
Minkä tahansa kielen elinkaaren aikana monet tällaiset uudelleenkäytettävät ratkaisut tekevät ja testaa suuri määrä kehittäjiä kyseisen kielen yhteisöstä. Tämän monien kehittäjien yhdistetyn kokemuksen ansiosta tällaiset ratkaisut ovat niin hyödyllisiä, koska ne auttavat meitä kirjoittamaan koodia optimoidulla tavalla ja samalla ratkaisemaan käsillä olevan ongelman.
Suunnittelumallien tärkeimmät edut ovat seuraavat:
Tiedän, että olet valmis hyppäämään tässä vaiheessa, mutta ennen kuin opit kaiken suunnittelumalleista, tarkastellaan joitain JavaScript-perusteita.
JavaScript on yksi suosituimmista verkkokehityksen ohjelmointikielistä. Alun perin se tehtiin eräänlaisena 'liimana' erilaisille näytetyille HTML-elementeille, jotka tunnetaan asiakaspuolen komentosarjakielenä, yhdelle alkuperäisistä selaimista. Netscape Navigator -nimellä se pystyi näyttämään vain staattista HTML-koodia. Kuten luulisi, ajatus tällaisesta komentosarjakielestä johti selainsotaan selainkehitysalan suurten toimijoiden välillä, kuten Netscape Communications (tänään Mozilla), Microsoft ja muut.
Jokainen suurista pelaajista halusi työntää läpi oman komentosarjakielensä, joten Netscape teki JavaScriptin (itse asiassa Brendan Eich teki), Microsoft teki JScriptin ja niin edelleen. Kuten voitte kuvata, erot näiden toteutusten välillä olivat suuria, joten verkkoselaimille kehitettiin selainkohtaisesti parhaiten katsottuja tarroja, jotka tulivat verkkosivun mukana. Pian kävi selväksi, että tarvitsemme standardin, selainten välisen ratkaisun, joka yhtenäistää kehitysprosessin ja yksinkertaistaa verkkosivujen luomista. Sille mitä he keksivät kutsutaan ECMAScript .
ECMAScript on standardoitu komentosarjakielimääritys, jota kaikki nykyaikaiset selaimet yrittävät tukea, ja ECMAScriptiä on useita (voisi sanoa murteita). Suosituin on tämän artikkelin aihe, JavaScript. Alkuperäisen julkaisunsa jälkeen ECMAScript on standardoinut paljon tärkeitä asioita, ja niille, jotka ovat kiinnostuneempia yksityiskohdista, on Wikipediassa saatavilla yksityiskohtainen luettelo standardoiduista tuotteista kullekin ECMAScript-versiolle. Selaintuki ECMAScript-versioille 6 (ES6) ja sitä uudemmille on edelleen puutteellinen, ja se on siirrettävä ES5: een, jotta sitä voidaan tukea kokonaan.
Jotta voisimme ymmärtää tämän artikkelin sisällön täysin, tehkäämme johdanto joihinkin erittäin tärkeisiin kieliominaisuuksiin, jotka meidän on oltava tietoisia ennen kuin sukeltaa JavaScript-suunnittelumalleihin. Jos joku kysyisi sinulta 'Mikä on JavaScript?' saatat vastata johonkin seuraavista riveistä:
JavaScript on kevyt, tulkittu, olio-ohjelmointikieli, jolla on ensiluokkaiset toiminnot, jotka tunnetaan yleisimmin verkkosivujen komentosarjakielenä.
Edellä mainittu määritelmä tarkoittaa sanomista, että JavaScript-koodilla on pieni muistinjälki, se on helppo toteuttaa ja helppo oppia, ja sen syntaksilla on samanlainen suosittujen kielten kuin C ++ ja Java kieli. Se on komentosarjakieli, mikä tarkoittaa, että sen koodi tulkitaan käännöksen sijaan. Se tukee menettelytapoja, olio- ja toiminnallisia ohjelmointityylejä, mikä tekee siitä erittäin joustavan kehittäjille.
Toistaiseksi olemme tarkastelleet kaikkia ominaisuuksia, jotka kuulostavat monilta muilta kieliltä, joten katsotaanpa, mikä JavaScriptissä on erityistä muille kielille. Luettelen muutamia ominaisuuksia ja annan parhaan kuvan selittääksesi, miksi ne ansaitsevat erityistä huomiota.
Tämä ominaisuus oli minulle hankala ymmärtää, kun olin vasta aloittamassa JavaScriptiä, koska tulin C / C ++ -taustasta. JavaScript kohtelee toimintoja ensiluokkaisina kansalaisina, eli voit välittää funktiot parametreina muille funktioille samalla tavalla kuin mitä tahansa muuta muuttujaa.
// we send in the function as an argument to be // executed from inside the calling function function performOperation(a, b, cb) { var c = a + b; cb(c); } performOperation(2, 3, function(result) { // prints out 5 console.log('The result of the operation is ' + result); })
Kuten monien muiden olioihin suuntautuvien kielten tapaan, JavaScript tukee objekteja, ja yksi ensimmäisistä termeistä, joka tulee mieleen ajatellessamme esineitä, ovat luokat ja perintö. Tässä se tulee hieman hankalaksi, koska kieli ei tue luokkia sen tavallisessa kielimuodossa, vaan käyttää pikemminkin prototyyppiin tai ilmentymiin perustuvaa perintöä.
Virallinen termi on juuri nyt ES6: ssa luokassa on otettu käyttöön, mikä tarkoittaa, että selaimet eivät vieläkään tue tätä (jos muistat kirjoituksen jälkeen, viimeinen täysin tuettu ECMAScript-versio on 5.1). On kuitenkin tärkeää huomata, että vaikka termi ”luokka” otetaan käyttöön JavaScriptissä, siinä käytetään silti prototyyppipohjaista perintöä.
Prototyyppipohjainen ohjelmointi on olio-ohjelmoinnin tyyli, jossa käyttäytymisen uudelleenkäyttö (tunnetaan nimellä perintö) suoritetaan prosessin avulla, jossa olemassa olevia objekteja käytetään uudelleen prototyyppeinä toimivien valtuuskuntien kautta. Sukellamme tähän tarkemmin, kun pääsemme artikkelin suunnittelumallien osioon, koska tätä ominaisuutta käytetään monissa JavaScript-suunnittelumalleissa.
Jos sinulla on kokemusta JavaScriptin käytöstä, olet varmasti perehtynyt termiin takaisinsoittotoiminto . Niille, jotka eivät tunne tätä termiä, soittopyyntö on funktio, joka lähetetään parametrina (muista, että JavaScript käsittelee toimintoja ensimmäisen luokan kansalaisina) toiselle toiminnolle ja suoritetaan tapahtuman laukeamisen jälkeen. Tätä käytetään yleensä tilata tapahtumia, kuten hiiren napsautus tai näppäimistön painikkeen painallus.
Joka kerta, kun tapahtuma, johon on liitetty kuuntelija, käynnistyy (muuten tapahtuma menetetään), viesti lähetetään synkronisesti käsiteltävien viestien jonoon FIFO-tavalla (first-in-first-out ). Tätä kutsutaan tapahtumasilmukka .
Jokaisella jonossa olevalla viestillä on siihen liittyvä toiminto. Kun viesti on poistettu käytöstä, ajonaikainen toiminto suoritetaan kokonaan ennen minkään muun viestin käsittelyä. Toisin sanoen, jos funktio sisältää muita toimintokutsuja, ne kaikki suoritetaan ennen uuden viestin käsittelyä jonosta. Tätä kutsutaan suoritukseksi loppuun.
while (queue.waitForMessage()) { queue.processNextMessage(); }
queue.waitForMessage()
odottaa synkronisesti uusia viestejä. Jokaisella käsiteltävällä viestillä on oma pino ja sitä käsitellään, kunnes pino on tyhjä. Kun se on valmis, uusi viesti käsitellään jonosta, jos sellaista on.
Olet ehkä myös kuullut, että JavaScriptiä ei estetä, mikä tarkoittaa, että kun asynkronista operaatiota suoritetaan, ohjelma pystyy käsittelemään muita asioita, kuten vastaanottamaan käyttäjän syötteitä, odottaen asynkronisen toiminnan päättymistä, ei estämällä pääkäyttäjää suorituslanka. Tämä on erittäin hyödyllinen JavaScriptin ominaisuus, ja tästä aiheesta voidaan kirjoittaa koko artikkeli; se ei kuitenkaan kuulu tämän artikkelin soveltamisalaan.
Kuten sanoin aiemmin, suunnittelumallit ovat uudelleenkäytettäviä ratkaisuja yleisesti esiintyviin ohjelmistosuunnittelun ongelmiin. Katsotaanpa joitain suunnittelumallien luokkia.
Kuinka luodaan malli? Oletetaan, että tunnistat yleisesti esiintyvän ongelman ja sinulla on oma ainutlaatuinen ratkaisu ongelmaan, jota ei ole tunnustettu ja dokumentoitu maailmanlaajuisesti. Käytät tätä ratkaisua aina, kun kohtaat tämän ongelman, ja luulet sen olevan uudelleenkäytettävissä ja että kehittäjäyhteisö voisi hyötyä siitä.
Tuleeko siitä heti malli? Onneksi ei. Usein voi olla hyviä koodin kirjoituskäytäntöjä ja yksinkertaisesti erehdyttää jotain, joka näyttää mallilta, vaikka itse asiassa se ei ole malli.
Mistä tiedät, milloin se, jonka luulet tunnistavan, on itse asiassa suunnittelumalli?
Hankkimalla muiden kehittäjien mielipiteet siitä, tuntemalla itse mallin luomisprosessi ja tutustumalla hyvin olemassa oleviin kuvioihin. On vaihe, jonka kuvion on käytävä läpi ennen kuin siitä tulee täysimittainen kuvio, ja tätä kutsutaan protokuviona.
Protokuvio on tuleva kuvio jos se läpäisee tietyn ajanjakson eri kehittäjät ja skenaariot, joissa malli osoittautuu hyödylliseksi ja antaa oikeat tulokset. Työtä ja dokumentaatiota - joista suurin osa ei kuulu tämän artikkelin piiriin - on tehtävä, jotta yhteisö tunnistaa täysimittaisen mallin.
Koska suunnittelumalli edustaa hyvää käytäntöä, anti-malli edustaa huonoa käytäntöä.
Esimerkki anti-mallista olisi Object
: n muokkaaminen luokan prototyyppi. Lähes kaikki JavaScript-objektit perivät Object
(muista, että JavaScript käyttää prototyyppipohjaista perintöä), joten kuvittele skenaario, jossa muutit tätä prototyyppiä. Muutokset Object
prototyyppi näkyisi kaikissa esineissä, jotka perivät tästä prototyypistä - mikä olisi suurin osa JavaScript-objektit . Tämä on katastrofi, joka odottaa tapahtumista.
Toinen esimerkki, samanlainen kuin yllä mainittu, on sellaisten objektien muokkaaminen, joita et omista. Esimerkki tästä on toiminnon ohittaminen objektista, jota käytetään monissa skenaarioissa koko sovelluksessa. Jos työskentelet suuren ryhmän kanssa, kuvittele hämmennystä, jonka tämä aiheuttaisi; joutuisit nopeasti nimeämään törmäyksiä, yhteensopimattomia toteutuksia ja ylläpito-painajaisia.
Samoin kuin on hyödyllistä tietää kaikista hyvistä käytännöistä ja ratkaisuista, on myös erittäin tärkeää tietää myös huonoista. Näin voit tunnistaa ne ja välttää tekemästä virhettä etukäteen.
Suunnittelumallit voidaan luokitella useilla tavoilla, mutta suosituin on seuraava:
Nämä mallit käsittelevät objektinluontimekanismeja, jotka optimoivat objektin luomisen perusmenetelmään verrattuna. Esineiden luomisen perusmuoto voi johtaa suunnitteluongelmiin tai lisätä suunnittelun monimutkaisuutta. Luovat suunnittelumallit ratkaisevat tämän ongelman kontrolloimalla jotenkin objektin luomista. Jotkut tämän luokan suosituimmista suunnittelumalleista ovat:
Nämä mallit käsittelevät objektisuhteita. Ne varmistavat, että jos yksi järjestelmän osa muuttuu, koko järjestelmän ei tarvitse muuttua sen mukana. Tämän kategorian suosituimmat mallit ovat:
Tämäntyyppiset mallit tunnistavat, toteuttavat ja parantavat järjestelmän erilaisten objektien välistä viestintää. Ne auttavat varmistamaan, että järjestelmän eri osissa on synkronoituja tietoja. Suosittuja esimerkkejä näistä malleista ovat:
Tämäntyyppiset suunnittelumallit käsittelevät monisäikeisiä ohjelmointiparadigmoja. Jotkut suosituimmista ovat:
Suunnittelumallit, joita käytetään arkkitehtonisiin tarkoituksiin. Jotkut tunnetuimmista ovat:
Seuraavassa osassa aiomme tarkastella lähemmin joitain edellä mainituista suunnittelumalleista, joiden esimerkkejä on annettu ymmärtämisen parantamiseksi.
Jokainen suunnittelumalli edustaa tietyntyyppistä ratkaisua tietyntyyppiseen ongelmaan. Ei ole olemassa universaaleja malleja, jotka sopivat aina parhaiten. Meidän on opittava, milloin tietty malli osoittautuu hyödylliseksi ja tarjoaako se todellista arvoa. Kun olemme perehtyneet malleihin ja skenaarioihin, joihin ne soveltuvat parhaiten, voimme helposti selvittää, soveltuuko tietty malli tiettyyn ongelmaan vai ei.
Muista, että väärän mallin soveltaminen tiettyyn ongelmaan voi johtaa ei-toivottuihin vaikutuksiin, kuten tarpeettomaan koodin monimutkaisuuteen, tarpeettomaan yleiskustannukseen suorituskyvyssä tai jopa uuden anti-mallin kutemiseen.
Nämä ovat kaikki tärkeitä asioita, jotka on otettava huomioon harkittaessa suunnittelumallin käyttämistä koodissamme. Aiomme tarkastella joitain suunnittelumalleja, jotka pidin henkilökohtaisesti hyödyllisinä, ja uskomme, että jokaisen vanhemman JavaScript-kehittäjän tulisi tuntea.
Kun ajatellaan klassisia olioihin suuntautuneita kieliä, konstruktori on luokan erikoistoiminto, joka alustaa objektin jollakin joukolla oletus- ja / tai lähetettyjä arvoja.
Tavallisia tapoja luoda objekteja JavaScriptissä ovat kolme seuraavaa tapaa:
// either of the following ways can be used to create a new object var instance = {}; // or var instance = Object.create(Object.prototype); // or var instance = new Object();
Objektin luomisen jälkeen on neljä tapaa (ES3: sta lähtien) lisätä ominaisuuksia näihin objekteihin. Ne ovat seuraavat:
// supported since ES3 // the dot notation instance.key = 'A key's value'; // the square brackets notation instance['key'] = 'A key's value'; // supported since ES5 // setting a single property using Object.defineProperty Object.defineProperty(instance, 'key', { value: 'A key's value', writable: true, enumerable: true, configurable: true }); // setting multiple properties using Object.defineProperties Object.defineProperties(instance, { 'firstKey': { value: 'First key's value', writable: true }, 'secondKey': { value: 'Second key's value', writable: false } });
Suosituin tapa luoda esineitä on kiharat sulkeet ja ominaisuuksien lisäämiseksi pistemerkinnät tai hakasulkeet. Jokainen, jolla on kokemusta JavaScriptistä, on käyttänyt niitä.
Mainitsimme aiemmin, että JavaScript ei tue natiivikursseja, mutta se tukee rakentajia käyttämällä funktiokutsuun etuliitettyä 'uutta' avainsanaa. Tällä tavalla voimme käyttää funktiota konstruktorina ja alustaa sen ominaisuudet samalla tavalla kuin tekisimme klassisen kielirakentajan kanssa.
// we define a constructor for Person objects function Person(name, age, isDeveloper) { this.name = name; this.age = age; this.isDeveloper = isDeveloper || false; this.writesCode = function() { console.log(this.isDeveloper? 'This person does write code' : 'This person does not write code'); } } // creates a Person instance with properties name: Bob, age: 38, isDeveloper: true and a method writesCode var person1 = new Person('Bob', 38, true); // creates a Person instance with properties name: Alice, age: 32, isDeveloper: false and a method writesCode var person2 = new Person('Alice', 32); // prints out: This person does write code person1.writesCode(); // prints out: this person does not write code person2.writesCode();
Tässä on kuitenkin vielä parantamisen varaa. Jos muistat, mainitsin aiemmin, että JavaScript käyttää prototyyppipohjaista perintöä. Edellisen lähestymistavan ongelmana on, että menetelmä writesCode
määritetään uudelleen jokaiselle Person
-esimerkille rakentaja. Voimme välttää tämän asettamalla menetelmän funktion prototyyppiin:
// we define a constructor for Person objects function Person(name, age, isDeveloper) false; // we extend the function's prototype Person.prototype.writesCode = function() { console.log(this.isDeveloper? 'This person does write code' : 'This person does not write code'); } // creates a Person instance with properties name: Bob, age: 38, isDeveloper: true and a method writesCode var person1 = new Person('Bob', 38, true); // creates a Person instance with properties name: Alice, age: 32, isDeveloper: false and a method writesCode var person2 = new Person('Alice', 32); // prints out: This person does write code person1.writesCode(); // prints out: this person does not write code person2.writesCode();
Nyt molemmat Person
konstruktori voi käyttää writesCode()
: n jaettua esiintymää menetelmä.
Mitä erikoisuuteen tulee, JavaScript ei koskaan lakkaa hämmästyttämästä. Toinen erikoinen asia JavaScriptille (ainakin objektiivisissa kielissä) on, että JavaScript ei tue pääsynmuokkaajia. Klassisella OOP-kielellä käyttäjä määrittelee luokan ja määrittää jäsenilleen käyttöoikeudet. Koska JavaScript ei tavallisessa muodossaan tue luokkia eikä käyttömuokkaajia, JavaScript-kehittäjät keksivät keinon jäljitellä tätä käyttäytymistä tarvittaessa.
Ennen kuin käsittelemme moduulimallien yksityiskohtia, puhutaan sulkemisen käsitteestä. A päättäminen on toiminto, jolla on pääsy ylätason piiriin, vaikka vanhempi toiminto olisi suljettu. Ne auttavat meitä jäljittelemään käyttömuokkaajien käyttäytymistä soveltamisalalla. Näytetään tämä esimerkin avulla:
// we used an immediately invoked function expression // to create a private variable, counter var counterIncrementer = (function() { var counter = 0; return function() { return ++counter; }; })(); // prints out 1 console.log(counterIncrementer()); // prints out 2 console.log(counterIncrementer()); // prints out 3 console.log(counterIncrementer());
Kuten näette, IIFE: n avulla olemme sitoneet laskurimuuttujan funktioon, joka on kutsuttu ja suljettu, mutta jota voidaan silti käyttää lapsen funktiolla, joka lisää sitä. Koska emme pääse laskurimuuttujaan funktiolausekkeen ulkopuolelta, teimme siitä yksityisen kattavuuden manipuloinnin avulla.
Sulkimien avulla voimme luoda esineitä, joissa on yksityisiä ja julkisia osia. Näitä kutsutaan moduulit ja ovat erittäin hyödyllisiä aina, kun haluamme piilottaa objektin tietyt osat ja paljastaa käyttöliittymän vain moduulin käyttäjälle. Näytetään tämä esimerkissä:
// through the use of a closure we expose an object // as a public API which manages the private objects array var collection = (function() { // private members var objects = []; // public members return { addObject: function(object) { objects.push(object); }, removeObject: function(object) { var index = objects.indexOf(object); if (index >= 0) { objects.splice(index, 1); } }, getObjects: function() { return JSON.parse(JSON.stringify(objects)); } }; })(); collection.addObject('Bob'); collection.addObject('Alice'); collection.addObject('Franck'); // prints ['Bob', 'Alice', 'Franck'] console.log(collection.getObjects()); collection.removeObject('Alice'); // prints ['Bob', 'Franck'] console.log(collection.getObjects());
Hyödyllisin asia, jonka tämä malli tuo käyttöön, on objektin yksityisten ja julkisten osien selkeä erottaminen, mikä on käsite, joka on hyvin samanlainen kuin klassisesta olio-taustasta tulevat kehittäjät.
Kaikki ei kuitenkaan ole niin täydellistä. Kun haluat muuttaa jäsenen näkyvyyttä, joudut muuttamaan koodia kaikkialla, missä olet käyttänyt tätä jäsentä, koska julkisten ja yksityisten osien käyttö on erilaista. Kohteeseen luomisen jälkeen lisätyt menetelmät eivät myöskään voi käyttää objektin yksityisiä jäseniä.
Tämä malli on parannus moduulikuvioon, kuten yllä on esitetty. Tärkein ero on, että kirjoitamme koko objektilogiikan moduulin yksityiseen alueeseen ja paljastamme sitten yksinkertaisesti osat, jotka haluamme olla julkisia palauttamalla nimettömän objektin. Voimme myös muuttaa yksityisten jäsenten nimeämistä, kun kartoitamme yksityisiä jäseniä vastaaviin julkisiin jäseniin.
// we write the entire object logic as private members and // expose an anonymous object which maps members we wish to reveal // to their corresponding public members var namesCollection = (function() { // private members var objects = []; function addObject(object) { objects.push(object); } function removeObject(object) { var index = objects.indexOf(object); if (index >= 0) { objects.splice(index, 1); } } function getObjects() { return JSON.parse(JSON.stringify(objects)); } // public members return { addName: addObject, removeName: removeObject, getNames: getObjects }; })(); namesCollection.addName('Bob'); namesCollection.addName('Alice'); namesCollection.addName('Franck'); // prints ['Bob', 'Alice', 'Franck'] console.log(namesCollection.getNames()); namesCollection.removeName('Alice'); // prints ['Bob', 'Franck'] console.log(namesCollection.getNames());
Paljastava moduulimalli on yksi ainakin kolmesta tapasta, jolla voimme toteuttaa moduulimallin. Ero paljastavan moduulimallin ja moduulimallin muiden muunnelmien välillä on ensisijaisesti siinä, miten julkisiin jäseniin viitataan. Tämän seurauksena paljastavaa moduulimallia on paljon helpompi käyttää ja muokata; se voi kuitenkin osoittautua hauraaksi tietyissä tilanteissa, kuten RMP-objektien käyttäminen prototyyppeinä perintöketjussa. Ongelmatilanteet ovat seuraavat:
Yksittäistä mallia käytetään tilanteissa, joissa tarvitsemme tarkalleen yhden luokan esiintymän. Esimerkiksi meillä on oltava objekti, joka sisältää jonkin kokoonpanon jollekin. Näissä tapauksissa ei ole välttämätöntä luoda uutta objektia aina, kun määritysobjektia tarvitaan jonnekin järjestelmässä.
var singleton = (function() { // private singleton value which gets initialized only once var config; function initializeConfiguration(values){ this.randomNumber = Math.random(); values = values || {}; this.number = values.number || 5; this.size = values.size || 10; } // we export the centralized method for retrieving the singleton value return { getConfig: function(values) { // we initialize the singleton value only once if (config === undefined) { config = new initializeConfiguration(values); } // and return the same config value wherever it is asked for return config; } }; })(); var configObject = singleton.getConfig({ 'size': 8 }); // prints number: 5, size: 8, randomNumber: someRandomDecimalValue console.log(configObject); var configObject1 = singleton.getConfig({ 'number': 8 }); // prints number: 5, size: 8, randomNumber: same randomDecimalValue as in first config console.log(configObject1);
Kuten esimerkistä näet, luotu satunnaisluku on aina sama, samoin kuin lähetetyt konfigurointiarvot.
On tärkeää huomata, että yhteyspisteen singleton-arvon noutamiseksi on oltava vain yksi ja hyvin tunnettu. Tämän mallin käytön haittapuoli on se, että sitä on melko vaikea testata.
Tarkkailumalli on erittäin hyödyllinen työkalu, kun meillä on skenaario, jossa meidän on parannettava järjestelmämme eri osien välistä viestintää optimoidulla tavalla. Se edistää löysää kytkentää esineiden välillä.
Tästä kuviosta on useita versioita, mutta perusmuodossaan meillä on kaksi pääosaa kuviosta. Ensimmäinen on aihe ja toinen on tarkkailijoita.
Kohde hoitaa kaikki tarkkailijoiden tilaamat aiheeseen liittyvät toimet. Nämä toiminnot tilata tarkkailijan tiettyyn aiheeseen, peruuttaa tarkkailijan tilauksen tietystä aiheesta ja ilmoittaa tarkkailijoille tietystä aiheesta, kun tapahtuma julkaistaan.
Tässä mallissa on kuitenkin muunnelma, jota kutsutaan julkaisija / tilaaja-malliksi, jota aion käyttää esimerkkinä tässä osiossa. Tärkein ero klassisen tarkkailijakuvion ja julkaisijan / tilaajamallin välillä on se, että julkaisija / tilaaja edistää vielä löyhempää kytkentää kuin tarkkailijamalli.
Tarkkailumallissa aihe pitää sisällään viitteet tilattuihin tarkkailijoihin ja kutsuu menetelmiä suoraan itse esineistä, kun taas julkaisija / tilaaja-mallissa meillä on kanavia, jotka toimivat viestintäsillana tilaajan ja julkaisijan välillä. Julkaisija käynnistää tapahtuman ja suorittaa vain kyseiselle tapahtumalle lähetetyn soittopyynnön.
Esitän lyhyen esimerkin kustantaja- / tilaajakuviosta, mutta kiinnostuneille klassinen tarkkailumalliesimerkki löytyy helposti verkosta.
var publisherSubscriber = {}; // we send in a container object which will handle the subscriptions and publishings (function(container) { // the id represents a unique subscription id to a topic var id = 0; // we subscribe to a specific topic by sending in // a callback function to be executed on event firing container.subscribe = function(topic, f) { if (!(topic in container)) { container[topic] = []; } container[topic].push({ 'id': ++id, 'callback': f }); return id; } // each subscription has its own unique ID, which we use // to remove a subscriber from a certain topic container.unsubscribe = function(topic, id) { var subscribers = []; for (var subscriber of container[topic]) { if (subscriber.id !== id) { subscribers.push(subscriber); } } container[topic] = subscribers; } container.publish = function(topic, data) { for (var subscriber of container[topic]) { // when executing a callback, it is usually helpful to read // the documentation to know which arguments will be // passed to our callbacks by the object firing the event subscriber.callback(data); } } })(publisherSubscriber); var subscriptionID1 = publisherSubscriber.subscribe('mouseClicked', function(data) { console.log('I am Bob's callback function for a mouse clicked event and this is my event data: ' + JSON.stringify(data)); }); var subscriptionID2 = publisherSubscriber.subscribe('mouseHovered', function(data) { console.log('I am Bob's callback function for a hovered mouse event and this is my event data: ' + JSON.stringify(data)); }); var subscriptionID3 = publisherSubscriber.subscribe('mouseClicked', function(data) { console.log('I am Alice's callback function for a mouse clicked event and this is my event data: ' + JSON.stringify(data)); }); // NOTE: after publishing an event with its data, all of the // subscribed callbacks will execute and will receive // a data object from the object firing the event // there are 3 console.logs executed publisherSubscriber.publish('mouseClicked', {'data': 'data1'}); publisherSubscriber.publish('mouseHovered', {'data': 'data2'}); // we unsubscribe from an event by removing the subscription ID publisherSubscriber.unsubscribe('mouseClicked', subscriptionID3); // there are 2 console.logs executed publisherSubscriber.publish('mouseClicked', {'data': 'data1'}); publisherSubscriber.publish('mouseHovered', {'data': 'data2'});
Tämä suunnittelumalli on hyödyllinen tilanteissa, joissa joudutaan suorittamaan useita toimintoja yhdellä laukaistavalla tapahtumalla. Kuvittele, että sinulla on skenaario, jossa meidän on soitettava useita AJAX-puheluita back-end-palvelulle ja suoritettava sitten muut AJAX-puhelut tuloksesta riippuen. Sinun pitäisi pestä AJAX-puhelut toisiinsa, mahdollisesti joutuessasi tilanteeseen, jota kutsutaan soittopyynnöksi. Kustantaja- / tilaajakuvion käyttäminen on paljon tyylikkäämpi ratkaisu.
Tämän mallin käytön haittapuoli on järjestelmän eri osien vaikea testaus. Meillä ei ole tyylikästä tapaa tietää, käyttäytyvätkö järjestelmän tilaajat odotetulla tavalla.
Käsittelemme lyhyesti mallia, joka on myös erittäin hyödyllinen puhuttaessa irrotetuista järjestelmistä. Kun meillä on skenaario, jossa järjestelmän useiden osien täytyy olla yhteydessä ja koordinoida, ehkä hyvä ratkaisu olisi sovittelijan käyttöönotto.
Välittäjä on esine, jota käytetään keskipisteenä järjestelmän erilaisten osien välisessä viestinnässä ja joka hoitaa niiden välisen työnkulun. Nyt on tärkeää korostaa, että se hoitaa työnkulun. Miksi tämä on tärkeää?
Koska samankaltaisuus julkaisijan / tilaajan mallin kanssa on suuri. Voit kysyä itseltäsi, OK, joten nämä kaksi mallia auttavat molempia toteuttamaan parempaa viestintää esineiden välillä ... Mikä on ero?
Erona on, että välittäjä hoitaa työnkulun, kun taas julkaisija / tilaaja käyttää jotain, jota kutsutaan 'tulipalo ja unohda' -tyyppiseksi viestinnäksi. Kustantaja / tilaaja on yksinkertaisesti tapahtumien koostaja, eli se yksinkertaisesti huolehtii tapahtumien laukaisemisesta ja oikeiden tilaajien tiedottamisesta tapahtumien laukaisemisesta. Tapahtumien yhdistäjällä ei ole väliä mitä tapahtuu tapahtuman laukeamisen jälkeen, mikä ei ole välittäjän tapausta.
Mukava esimerkki sovittelijasta on ohjatun tyyppinen käyttöliittymä. Oletetaan, että sinulla on laaja rekisteröintiprosessi järjestelmään, jossa olet työskennellyt. Usein, kun käyttäjältä vaaditaan paljon tietoa, on hyvä tapa jakaa se useaan vaiheeseen.
Tällä tavalla koodi on paljon puhtaampi (helpompi ylläpitää), eikä käyttäjä ole ylikuormitettu tietojen määrästä, jota vaaditaan vain rekisteröinnin loppuun saattamiseksi. Välittäjä on objekti, joka hoitaisi rekisteröintivaiheet ottaen huomioon erilaiset mahdolliset työnkulut, joita voi tapahtua siitä syystä, että jokaisella käyttäjällä voi olla yksilöllinen rekisteröintiprosessi.
Tämän suunnittelumallin ilmeinen hyöty on parannettu viestintä järjestelmän eri osien välillä, jotka nyt kaikki kommunikoivat välittäjän ja puhtaamman koodipohjan kautta.
Haittapuolena olisi, että nyt olemme lisänneet järjestelmäämme yhden epäonnistumispisteen, eli jos sovittelijamme epäonnistuu, koko järjestelmä voi lakata toimimasta.
Kuten olemme jo maininneet artikkelissa, JavaScript ei tue luokkia sen alkuperäisessä muodossa. Objektien välinen perintö toteutetaan prototyyppipohjaisen ohjelmoinnin avulla.
Sen avulla voimme luoda esineitä, jotka voivat toimia prototyyppinä muille luotaville kohteille. Prototyyppiobjektia käytetään suunnitelmana jokaiselle konstruktorin luomalle objektille.
Koska olemme jo puhuneet tästä edellisissä osioissa, näytetään yksinkertainen esimerkki siitä, miten tätä mallia voidaan käyttää.
var personPrototype = { sayHi: function() { console.log('Hello, my name is ' + this.name + ', and I am ' + this.age); }, sayBye: function() { console.log('Bye Bye!'); } }; function Person(name, age) { name = name || 'John Doe'; age = age || 26; function constructorFunction(name, age) { this.name = name; this.age = age; }; constructorFunction.prototype = personPrototype; var instance = new constructorFunction(name, age); return instance; } var person1 = Person(); var person2 = Person('Bob', 38); // prints out Hello, my name is John Doe, and I am 26 person1.sayHi(); // prints out Hello, my name is Bob, and I am 38 person2.sayHi();
Ota huomioon, kuinka prototyyppiperintö lisää myös suorituskykyä, koska molemmat objektit sisältävät viittauksen toimintoihin, jotka toteutetaan itse prototyypissä kussakin objektissa.
Komentomalli on hyödyllinen tapauksissa, joissa haluamme irrottaa komennot suorittavat objektit komentoja antavista objekteista. Kuvittele esimerkiksi skenaario, jossa sovelluksemme käyttää suurta määrää API-palvelupyyntöjä. Oletetaan sitten, että API-palvelut muuttuvat. Meidän olisi muokattava koodia missä tahansa muuttuvia sovellusliittymiä kutsutaan.
Tämä olisi hyvä paikka toteuttaa abstraktiokerros, joka erottaisi API-palvelua kutsuvat objektit niistä, jotka kertovat heille kun soittaa API-palveluun. Tällä tavoin vältämme muokkaamista kaikissa paikoissa, joissa meidän on tarpeen soittaa palveluun, vaan meidän on pikemminkin vaihdettava vain ne kohteet, jotka itse soittavat, mikä on vain yksi paikka.
Kuten minkä tahansa muun mallin kohdalla, meidän on tiedettävä, milloin tällaiselle mallille on todellinen tarve. Meidän on oltava tietoisia tekemästämme kompromissista, koska lisäämme ylimääräisen abstraktiokerroksen API-kutsuihin, mikä vähentää suorituskykyä, mutta säästää mahdollisesti paljon aikaa, kun meidän on muokattava komentoja suorittavia objekteja.
// the object which knows how to execute the command var invoker = { add: function(x, y) { return x + y; }, subtract: function(x, y) { return x - y; } } // the object which is used as an abstraction layer when // executing commands; it represents an interface // toward the invoker object var manager = { execute: function(name, args) { if (name in invoker) { return invoker[name].apply(invoker, [].slice.call(arguments, 1)); } return false; } } // prints 8 console.log(manager.execute('add', 3, 5)); // prints 2 console.log(manager.execute('subtract', 5, 3));
Julkisivukuviota käytetään, kun haluamme luoda abstraktikerroksen julkisesti esitetyn ja verhon takana toteutetun välille. Sitä käytetään, kun halutaan helpompaa tai yksinkertaisempaa käyttöliittymää taustalla olevaan objektiin.
Erinomainen esimerkki tästä mallista olisi valitsimet DOM-manipulointikirjastoista, kuten jQuery, Dojo tai D3. Olet ehkä huomannut näiden kirjastojen käyttämisen, että niillä on erittäin tehokkaat valitsinominaisuudet; voit kirjoittaa monimutkaisiin kyselyihin, kuten:
jQuery('.parent .child div.span')
Se yksinkertaistaa valinnan ominaisuuksia paljon, ja vaikka se näyttääkin pinnalta yksinkertaiselta, on hupun alla toteutettu koko monimutkainen logiikka, jotta tämä toimisi.
Meidän on myös oltava tietoisia suorituskyvyn ja yksinkertaisuuden kompromissista. On toivottavaa välttää ylimääräistä monimutkaisuutta, jos se ei ole tarpeeksi hyödyllistä. Edellä mainittujen kirjastojen tapauksessa vaihto oli sen arvoinen, koska ne kaikki ovat erittäin onnistuneita kirjastoja.
Suunnittelumallit ovat erittäin hyödyllinen työkalu vanhempi JavaScript-kehittäjä pitäisi olla tietoinen. Suunnittelumallien erityispiirteiden tunteminen voi osoittautua uskomattoman hyödylliseksi ja säästää paljon aikaa minkä tahansa projektin elinkaaressa, etenkin huolto-osassa. Järjestelmän muokkaaminen ja ylläpito suunnitelmien avulla, jotka sopivat hyvin järjestelmän tarpeisiin, voi osoittautua korvaamattomaksi.
Jotta artikkeli olisi suhteellisen lyhyt, emme näytä enää esimerkkejä. Kiinnostuneille suuri inspiraatio tähän artikkeliin tuli Neljän jengi -kirjasta Suunnittelumallit: Uudelleenkäytettävien olio-ohjelmistojen elementit ja Addy Osmanin JavaScript-suunnittelumallien oppiminen . Suosittelen molempia kirjoja.
Liittyvät: JS-kehittäjänä tämä pitää minut yllä yöllä / tuntee ES6-luokan sekaannustaJavaScript on asynkroninen, tukee ensiluokkaisia toimintoja ja on prototyyppipohjainen.
Suunnittelumallit ovat uudelleenkäytettäviä ratkaisuja yleisiin ohjelmistosuunnittelun ongelmiin. Ne ovat todistettuja ratkaisuja, helposti uudelleenkäytettäviä ja ilmeikkäitä. Ne pienentävät kooditietokantasi kokoa, estävät tulevaa korjaamista ja helpottavat koodisi ymmärtämistä muille kehittäjille.
JavaScript on asiakaspuolen komentosarjakieli selaimille, jonka Brendan Eich loi ensimmäisenä Netscape Navigatorille nykyisen Mozillan toimesta.
ECMAScript on standardoitu komentosarjakielimääritys, jota kaikki nykyaikaiset selaimet yrittävät tukea. ECMAScript-ohjelmistoa on useita, joista suosituin on JavaScript.
Protokuvio on tuleva malli, jos se läpäisee tietyn testausjakson eri kehittäjiltä ja skenaarioista, joissa malli osoittautuu hyödylliseksi ja antaa oikeat tulokset.
Jos suunnittelumalli edustaa hyvää käytäntöä, niin anti-malli edustaa huonoa käytäntöä. Esimerkki anti-mallista olisi Object-luokan prototyypin muokkaaminen. Object-prototyypin muutokset näkyvät kaikissa objekteissa, jotka perivät tästä prototyypistä (eli melkein kaikista JavaScript-objekteista).
Suunnittelumallit voivat olla luovia, rakenteellisia, käyttäytymiseen liittyviä, samanaikaisia tai arkkitehtonisia.
Joitakin yllä olevassa artikkelissa käsiteltyjä esimerkkejä ovat konstruktorikuvio, moduulikuvio, paljastava moduulikuvio, yksikkökuvio, havainnointikuvio, välittäjäkuvio, prototyyppikuvio, komentokuvio ja julkisivumalli.