JavaScript Design Patterns: The Observer Pattern

in JavaScript is er een probleem dat vaak opkomt. U hebt een manier nodig om delen van een pagina bij te werken als reactie op bepaalde gebeurtenissen, met de gegevens die deze bieden. Bijvoorbeeld, gebruikersinvoer die je vervolgens projecteert in een of meerdere componenten. Dit leidt tot veel push-and-pull in de code om alles synchroon te houden.

Dit is waar het ontwerppatroon van de waarnemer kan helpen – het maakt een-tot-veel gegevensbinding tussen elementen mogelijk. Deze eenrichtingsgegevensbinding kan event driven zijn. Met dit patroon, kunt u herbruikbare code die oplost voor uw specifieke behoeften te bouwen.

In dit artikel wil ik graag het observator design patroon verkennen. Het zal u helpen bij het oplossen van een veelvoorkomend probleem dat u ziet in client-side scripting. Dat is een-op-veel, een-weg, en event-driven data binding. Het is een probleem dat vaak opkomt als je veel elementen hebt die synchroon moeten zijn.

Ik gebruik ECMAScript 6 om het patroon te illustreren. Ja, Er zullen klassen, pijlfuncties en constanten zijn. Voel je vrij om deze onderwerpen te verkennen op uw eigen als je niet al bekend bent. Ik zal delen van ES6 gebruiken die alleen syntactische suiker introduceren, dus het is draagbaar met ES5 indien nodig.

en Ik zal Test-Driven-Development (TDD) gebruiken om aan het patroon te werken. Op deze manier heb je een manier weten hoe elk onderdeel is nuttig.

de nieuwe taalfuncties in ES6 zorgen voor een beknopte code. Dus, laten we beginnen.

the Event Observer

een weergave op hoog niveau van het patroon ziet er als volgt uit:

EventObserver│ ├── subscribe: adds new observable events│ ├── unsubscribe: removes observable events|└── broadcast: executes all events with bound data

nadat ik het patroon van de waarnemer heb uitgewerkt, voeg ik een woordentelling toe die het gebruikt. Het woord count component zal deze waarnemer nemen en alles samenbrengen.

om de EventObserver te initialiseren:

class EventObserver { constructor() { this.observers = ; }}

begin met een lege lijst van waargenomen gebeurtenissen, en doe dit voor elke nieuwe instantie. Laten we vanaf nu meer methoden toevoegen binnen EventObserver om het ontwerppatroon uit te werken.

de Subscribe-methode

om nieuwe gebeurtenissen toe te voegen:

subscribe(fn) { this.observers.push(fn);}

pak de lijst met waargenomen gebeurtenissen en duw een nieuw item naar de array. De lijst van gebeurtenissen is een lijst van callback functies.

een manier om deze methode in gewoon JavaScript te testen is als volgt:

// Arrangeconst observer = new EventObserver();const fn = () => {};// Actobserver.subscribe(fn);// Assertassert.strictEqual(observer.observers.length, 1);

Ik gebruik node beweringen om deze component te testen in Node. Precies dezelfde beweringen bestaan als Chai beweringen ook.

de lijst van waargenomen gebeurtenissen bestaat uit bescheiden callbacks. We controleren vervolgens de lengte van de lijst en bevestigen dat de callback op de lijst staat.

de Afmeldmethode

om gebeurtenissen te verwijderen:

unsubscribe(fn) { this.observers = this.observers.filter((subscriber) => subscriber !== fn);}

Filter uit de lijst wat overeenkomt met de callback functie. Als er geen overeenkomst is, mag de callback op de lijst blijven. Het filter geeft een nieuwe lijst terug en wijst de lijst met waarnemers opnieuw toe.

om deze mooie methode te testen, doe:

// Arrangeconst observer = new EventObserver();const fn = () => {};observer.subscribe(fn);// Actobserver.unsubscribe(fn);// Assertassert.strictEqual(observer.observers.length, 0);

de callback moet overeenkomen met dezelfde functie die op de lijst staat. Als er een overeenkomst is, verwijdert de afmeldmethode deze uit de lijst. Merk op dat de test de functieverwijzing gebruikt om het toe te voegen en te verwijderen.

de Broadcast-methode

om alle gebeurtenissen aan te roepen:

broadcast(data) { this.observers.forEach((subscriber) => subscriber(data));}

dit itereert door de lijst van waargenomen gebeurtenissen en voert alle callbacks uit. Met deze, krijg je de nodige one-to-many relatie met de geabonneerde evenementen. Je geeft de data parameter door die de callback data gebonden maakt.

ES6 maakt de code effectiever met een pijl-functie. Let op de (subscriber) => subscriber(data) functie die het meeste werk doet. Deze one-liner arrow functie profiteert van deze korte ES6 syntaxis. Dit is een duidelijke verbetering in de programmeertaal JavaScript.

om deze broadcast methode te testen, doe:

// Arrangeconst observer = new EventObserver();let subscriberHasBeenCalled = false;const fn = (data) => subscriberHasBeenCalled = data;observer.subscribe(fn);// Actobserver.broadcast(true);// Assertassert(subscriberHasBeenCalled);

gebruik let in plaats van een const zodat we de waarde van de variabele kunnen wijzigen. Dit maakt de variabele veranderlijk waardoor ik de waarde binnen de callback opnieuw kan toewijzen. Het gebruik van een let in je code stuurt een signaal naar andere programmeurs dat de variabele op een bepaald moment verandert. Dit voegt leesbaarheid en duidelijkheid toe aan uw JavaScript-code.

deze test geeft mij het vertrouwen dat nodig is om ervoor te zorgen dat de waarnemer werkt zoals ik verwacht. Met TDD, het is allemaal over het bouwen van herbruikbare code in gewone JavaScript. Er zijn voordelen aan het schrijven van testbare code in gewone JavaScript. Test alles en bewaar wat goed is voor code hergebruik.

hiermee hebben we de EventObserveraangevuld. De vraag is, wat kun je hiermee bouwen?

The Observer Pattern in Action: A Blog Word Count Demo

voor de demo, tijd om een blogpost op te zetten waar het het aantal woorden voor u bewaart. Elke toetsaanslag die u invoert, wordt gesynchroniseerd door het observer design pattern. Zie het als vrije tekstinvoer waarbij elk evenement een update afvuurt naar waar je het nodig hebt.

om een woordaantal van vrije tekstinvoer te krijgen, kan men doen:

const getWordCount = (text) => text ? text.trim().split(/\s+/).length : 0;

klaar! Er is veel aan de hand in deze schijnbaar eenvoudige pure functie, dus wat dacht je van een bescheiden unit test? Op deze manier is duidelijk wat ik van plan was dit te doen:

// Arrangeconst blogPost = 'This is a blog \n\n post with a word count. ';// Actconst count = getWordCount(blogPost);// Assertassert.strictEqual(count, 9);

noteer de enigszins maffe invoerstring binnen blogPost. Ik ben van plan om met deze functie zoveel mogelijk edge cases te behandelen. Zolang het me een goede woordtelling geeft, gaan we in feite de goede kant op.

dit is de werkelijke kracht van TDD. Men kan deze implementatie herhalen en zoveel mogelijk use cases behandelen. De unit test vertelt je hoe ik verwacht dat dit zich zal gedragen. Als het gedrag een fout heeft, om welke reden dan ook, is het gemakkelijk om het te herhalen en te tweaken. Met de test, is er genoeg bewijs achtergelaten voor een andere persoon om veranderingen aan te brengen.

tijd om deze herbruikbare componenten aan de DOM te koppelen. Dit is het deel waar je gewoon JavaScript hanteren en las het recht in de browser.

een manier om dit te doen zou zijn om de volgende HTML op de pagina te hebben:

<textarea placeholder="Enter your blog post..." class="blogPost"></textarea>

gevolgd door dit JavaScript:

const wordCountElement = document.createElement('p');wordCountElement.className = 'wordCount';wordCountElement.innerHTML = 'Word Count: <strong>0</strong>';document.body.appendChild(wordCountElement);const blogObserver = new EventObserver();blogObserver.subscribe((text) => { const blogCount = document.getElementById('blogWordCount'); blogCount.textContent = getWordCount(text);});const blogPost = document.getElementById('blogPost');blogPost.addEventListener('keyup', () => blogObserver.broadcast(blogPost.value));

neem al uw herbruikbare code en zet in plaats van de waarnemer ontwerppatroon. Dit zal wijzigingen in het tekstgebied bijhouden en geeft u een woordtelling recht eronder. Ik gebruik body.appendChild() in de DOM API om dit nieuwe element toe te voegen. Dan, het bevestigen van de gebeurtenis luisteraars om het tot leven te brengen.

opmerking met pijlfuncties is het mogelijk om one-liner events aan te sluiten. In feite, u uitgezonden event-driven veranderingen aan alle abonnees met deze. De () => blogObserver.broadcast() doet het grootste deel van het werk hier. Het gaat zelfs in de laatste wijzigingen aan het tekstgebied recht in de callback-functie. Ja, client-side scripting is super cool.

geen demo is compleet zonder een die je kunt aanraken en aanpassen, hieronder is de CodePen:

zie de Pen het Waarnemerspatroon door SitePoint (@SitePoint) op CodePen.

nu zou ik deze functie niet compleet noemen. Het is slechts een uitgangspunt van het observator-ontwerppatroon. De vraag in mijn gedachten is, hoe ver ben je bereid te gaan?

vooruitblikkend

het is aan u om dit idee nog verder uit te werken. Er zijn vele manieren waarop u het observer design pattern kunt gebruiken om nieuwe functies te bouwen.

u kunt de demo verbeteren met:

  • een ander onderdeel dat het aantal alinea ‘ s
  • telt een ander onderdeel dat een voorbeeld van ingevoerde tekst toont
  • verbeter het voorbeeld met markdown-ondersteuning, bijvoorbeeld

dit zijn slechts een paar ideeën die u kunt doen om dieper hierin te zinken. De verbeteringen hierboven zullen uw programmering chops uitdagen.

conclusie

het observator-ontwerppatroon kan u helpen bij het oplossen van echte problemen in JavaScript. Dit lost het eeuwige probleem van het houden van een bos van elementen gesynchroniseerd met dezelfde gegevens. Zoals vaak het geval is, wanneer de browser branden specifieke gebeurtenissen. Ik weet zeker dat de meesten van jullie nu over een dergelijk probleem zijn gekomen en hebben gerend in de richting van tools en afhankelijkheden van derden.

dit ontwerppatroon stelt u in staat om zo ver te gaan als uw verbeelding bereid is te gaan. Bij het programmeren, je abstract de oplossing in een patroon en bouwen herbruikbare code. Er is geen limiet aan hoe ver dit je zal brengen.

ik hoop dat u ziet hoeveel, met een beetje discipline en inspanning, u kunt doen in gewoon JavaScript. De nieuwe functies in de taal, zoals ES6, helpen u bij het schrijven van een aantal beknopte code die herbruikbaar is.

dit artikel werd peer reviewed door Giulio Mainardi. Met dank aan alle peer reviewers van SitePoint voor het optimaal maken van SitePoint content!

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.