Ontwerppatronen in JavaScript begrijpen

Tegenwoordig gaan we onze computerwetenschaps-hoeden omdoen terwijl we meer te weten komen over enkele veelvoorkomende ontwerppatronen. Ontwerppatronen bieden ontwikkelaars manieren om technische problemen op een herbruikbare en elegante manier op te lossen. Interesse om een ​​betere JavaScript-ontwikkelaar te worden? Lees dan verder.

Opnieuw gepubliceerde zelfstudie

Om de paar weken bekijken we enkele van onze favoriete lezers uit de geschiedenis van de site. Deze tutorial is voor het eerst gepubliceerd in juli 2012.


Invoering

Stevige ontwerppatronen vormen de basisbouwsteen voor onderhoudbare softwaretoepassingen. Als je ooit aan een technisch interview hebt deelgenomen, werd je daarnaar gevraagd. In deze zelfstudie bekijken we enkele patronen die u vandaag kunt gebruiken.


Wat is een ontwerppatroon?

Een ontwerppatroon is een herbruikbare software-oplossing

Eenvoudig gezegd, een ontwerppatroon is een herbruikbare software-oplossing voor een specifiek type probleem dat vaak voorkomt bij het ontwikkelen van software. Gedurende de vele jaren van het oefenen van softwareontwikkeling, hebben experts manieren bedacht om soortgelijke problemen op te lossen. Deze oplossingen zijn ingekapseld in ontwerppatronen. Zo:

  • patronen zijn bewezen oplossingen voor problemen met de ontwikkeling van software
  • patronen zijn schaalbaar omdat ze meestal gestructureerd zijn en regels bevatten die je moet volgen
  • patronen zijn herbruikbaar voor vergelijkbare problemen

In de tutorial komen we enkele voorbeelden van ontwerppatronen tegen.


Soorten ontwerppatronen

Bij de ontwikkeling van software worden ontwerppatronen over het algemeen gegroepeerd in een paar categorieën. We behandelen de drie belangrijkste in deze zelfstudie. Ze worden hieronder in het kort uitgelegd:

  1. Creational patronen richten zich op manieren om objecten of klassen te maken. Dit klinkt misschien eenvoudig (en dat is het in sommige gevallen), maar grote toepassingen moeten het proces voor het maken van het object besturen.
  2. Structureel ontwerppatronen richten zich op manieren om relaties tussen objecten te beheren, zodat uw applicatie op een schaalbare manier wordt ontworpen. Een belangrijk aspect van structurele patronen is ervoor te zorgen dat een verandering in een deel van uw toepassing niet van invloed is op alle andere delen.
  3. Gedragsmatig patronen zijn gericht op communicatie tussen objecten.

U kunt nog steeds vragen hebben na het lezen van deze korte beschrijvingen. Dit is normaal, en dingen zullen duidelijk worden zodra we kijken naar enkele ontwerppatronen hieronder dieper. Dus lees verder!


Een opmerking over klassen in JavaScript

Wanneer u leest over ontwerppatronen, ziet u vaak verwijzingen naar klassen en objecten. Dit kan verwarrend zijn, omdat JavaScript niet echt het concept 'klasse' heeft; een correctere term is "gegevenstype".

Datatypes in JavaScript

JavaScript is een objectgeoriënteerde taal waarin objecten worden overgenomen van andere objecten in een concept dat bekend staat als prototypische overerving. Een gegevenstype kan worden gemaakt door te definiëren wat een constructorfunctie wordt genoemd, zoals deze:

function Person (config) this.name = config.name; this.age = config.age;  Person.prototype.getAge = function () return this.age; ; var tilo = new Person (name: "Tilo", age: 23); console.log (tilo.getAge ());

Let op het gebruik van de prototype bij het definiëren van methoden op de Persoon data type. Sinds meerdere Persoon objecten zullen verwijzen naar hetzelfde prototype, dit staat de getAge () methode die gedeeld moet worden door alle instanties van de Persoon gegevenstype, in plaats van het opnieuw te definiëren voor elk exemplaar. Bovendien, elk gegevenstype dat erft van Persoon zal toegang hebben tot de getAge () methode.

Omgaan met privacy

Een ander veel voorkomend probleem in JavaScript is dat er geen echt gevoel van privévariabelen bestaat. We kunnen echter sluitingen gebruiken om privacy enigszins te simuleren. Beschouw het volgende fragment:

var retinaMacbook = (function () // Privévariabelen var RAM, addRAM; RAM = 4; // Privémethode addRAM = functie (additionalRAM) RAM + = additionalRAM;; return // Openbare variabelen en methoden USB: undefined , insertUSB: function (device) this.USB = device;, removeUSB: function () var device = this.USB; this.USB = undefined; return device;;) ();

In het bovenstaande voorbeeld hebben we een gemaakt retinaMacbook object, met openbare en privévariabelen en methoden. Dit is hoe we het zouden gebruiken:

retinaMacbook.insertUSB ( "myUSB"); console.log (retinaMacbook.USB); // logt uit "myUSB" console.log (retinaMacbook.RAM) // logt uit ongedefinieerd

Er is veel meer dat we kunnen doen met functies en sluitingen in JavaScript, maar we zullen hier niet alles over vertellen in deze zelfstudie. Met deze kleine les over JavaScript-gegevenstypen en privacy achter ons, kunnen we meedoen om meer te weten te komen over ontwerppatronen.


Creationele ontwerppatronen

Er zijn veel verschillende soorten ontwerppatronen voor creaties, maar we gaan er twee bespreken in deze tutorial: Builder en Prototype. Ik merk dat deze vaak genoeg worden gebruikt om de aandacht te trekken.

Bouwer patroon

Het builderpatroon wordt vaak gebruikt bij webontwikkeling en u hebt het waarschijnlijk al eerder gebruikt zonder het te beseffen. Simpel gezegd, dit patroon kan als volgt worden gedefinieerd:

Door het builderpatroon toe te passen, kunnen we objecten construeren door alleen het type en de inhoud van het object op te geven. We hoeven het object niet expliciet te maken.

U hebt dit bijvoorbeeld waarschijnlijk ontelbare keren gedaan in jQuery:

var myDiv = $ ('
Dit is een div.
'); // myDiv vertegenwoordigt nu een jQuery-object dat verwijst naar een DOM-knooppunt. var someText = $ ('

'); // someText is een jQuery-object dat verwijst naar een HTMLParagraphElement var input = $ ('');

Bekijk de drie bovenstaande voorbeelden. In de eerste hebben we een a

element met wat inhoud. In de tweede gingen we leeg voorbij

label. In de vorige passeerden we een element. Het resultaat van alle drie was hetzelfde: we kregen een jQuery-object terug dat verwijst naar een DOM-knooppunt.

De $ variabele gebruikt het builderpatroon in jQuery. In elk voorbeeld hebben we een jQuery DOM-object geretourneerd en toegang gekregen tot alle methoden die door de jQuery-bibliotheek zijn geleverd, maar op geen enkel moment hebben we expliciet gebeld document.createElement. De JS-bibliotheek heeft dit alles onder de motorkap afgehandeld.

Stel je voor hoe veel werk het zou zijn als we het DOM-element expliciet zouden moeten maken en er inhoud in zouden moeten voegen! Door gebruik te maken van het bouwerspatroon kunnen we ons concentreren op het type en de inhoud van het object, in plaats van het expliciet te maken.

Prototype patroon

We hebben eerder besproken hoe datatypes in JavaScript kunnen worden gedefinieerd door middel van functies en methoden aan het object toe te voegen prototype. Het prototype-patroon maakt het mogelijk objecten te erven van andere objecten, via hun prototypen.

Het prototype patroon is een patroon waarbij objecten worden gemaakt op basis van een sjabloon van een bestaand object door middel van klonen.

Dit is een eenvoudige en natuurlijke manier om overerving in JavaScript te implementeren. Bijvoorbeeld:

var Persoon = numFeet: 2, numHeads: 1, numHands: 2; //Object.create neemt het eerste argument en past het toe op het prototype van uw nieuwe object. var tilo = Object.create (persoon); console.log (tilo.numHeads); // uitgangen 1 tilo.numHeads = 2; console.log (tilo.numHeads) // uitgangen 2

De eigenschappen (en methoden) in de Persoon object wordt toegepast op het prototype van de tilo voorwerp. We kunnen de eigenschappen op de tilo object als we willen dat ze anders zijn.

In het bovenstaande voorbeeld hebben we gebruikt Object.create (). Internet Explorer 8 biedt echter geen ondersteuning voor de nieuwere methode. In deze gevallen kunnen we het gedrag ervan simuleren:

var vehiclePrototype = init: function (carModel) this.model = carModel; , getModel: function () console.log ("Het model van dit voertuig is" + this.model); ; function vehicle (model) functie F () ; F.prototype = voertuigPrototype; var f = nieuwe F (); f.init (model); terugkeer f;  var car = vehicle ("Ford Escort"); car.getModel ();

Het enige nadeel van deze methode is dat u geen alleen-lezen eigenschappen kunt specificeren, die kunnen worden gespecificeerd tijdens het gebruik Object.create (). Niettemin laat het prototype patroon zien hoe objecten kunnen erven van andere objecten.


Structurele ontwerppatronen

Structurele ontwerppatronen zijn erg handig bij het uitzoeken hoe een systeem zou moeten werken. Hierdoor kunnen onze applicaties gemakkelijk opschalen en onderhouden blijven. We gaan de volgende patronen in deze groep bekijken: composiet en gevel.

Samengesteld patroon

Het samengestelde patroon is een ander patroon dat u waarschijnlijk eerder hebt gebruikt zonder enige realisatie.

Het samengestelde patroon zegt dat een groep objecten op dezelfde manier kan worden behandeld als een individueel object van de groep.

Dus wat betekent dit? Overweeg dit voorbeeld in jQuery (de meeste JS-bibliotheken hebben dit equivalent):

$ ( 'MyList ') addClass (' geselecteerd.'); . $ ( '# MyItem') addClass ( 'geselecteerd'); // doe dit niet op grote tafels, het is maar een voorbeeld. $ ("# dataTable tbody tr"). on ("klik", functie (gebeurtenis) alert ($ (this) .text ());); $ ('# myButton'). on ("klik", functie (event) alert ("Clicked."););

De meeste JavaScript-bibliotheken bieden een consistente API, ongeacht of we te maken hebben met een enkel DOM-element of een array met DOM-elementen. In het eerste voorbeeld kunnen we de gekozen klasse voor alle items opgehaald door de .mijn lijst selector, maar we kunnen dezelfde methode gebruiken als het gaat om een ​​enkelvoudig DOM-element, #myItem. Evenzo kunnen we gebeurtenishandlers koppelen met behulp van de op() methode op meerdere knooppunten, of op een enkel knooppunt via dezelfde API.

Door gebruik te maken van het Composite-patroon, bieden jQuery (en vele andere bibliotheken) ons een vereenvoudigde API.

Het composietpatroon kan soms ook problemen veroorzaken. In een losjes getypte taal zoals JavaScript, kan het vaak handig zijn om te weten of we te maken hebben met een enkel element of meerdere elementen. Omdat het samengestelde patroon voor beide dezelfde API gebruikt, kunnen we vaak de ene voor de andere verwarren en eindigen met onverwachte fouten. Sommige bibliotheken, zoals YUI3, bieden twee afzonderlijke methoden om elementen te krijgen (Y.one () vs Jullie allemaal()).

Gevelpatroon

Hier is een ander veelvoorkomend patroon dat we als vanzelfsprekend beschouwen. In feite is dit een van mijn favorieten omdat het eenvoudig is en ik heb gezien dat het overal wordt gebruikt om te helpen met inconsistenties van de browser. Dit is waar het Gevelpatroon over gaat:

Het gevelpatroon biedt de gebruiker een eenvoudige interface en verbergt de onderliggende complexiteit.

Het Gevelpatroon verbetert bijna altijd de bruikbaarheid van een stukje software. Het gebruik van jQuery als een voorbeeld weer, een van de meer populaire methoden van de bibliotheek is de klaar() methode:

$ (document) .ready (function () // al uw code komt hier ...);

De klaar() methode implementeert eigenlijk een gevel. Als je de bron bekijkt, vind je dit:

ready: (function () ... // Mozilla, Opera en Webkit if (document.addEventListener) document.addEventListener ("DOMContentLoaded", idempotent_fn, false); ... // IE-gebeurtenismodel anders if (document.attachEvent) // zorg ervoor dat u vuurt vóór onload; misschien te laat maar veilig ook voor iframes document.attachEvent ("onreadystatechange", idempotent_fn); // Een fallback naar window.onload, dat werkt altijd window.attachEvent ("onload", idempotent_fn); ...)

Onder de motorkap, de klaar() methode is niet zo eenvoudig. jQuery normaliseert de inconsistenties van de browser om dat te garanderen klaar() wordt op het juiste moment afgevuurd. Als ontwikkelaar krijgt u echter een eenvoudige interface te zien.

De meeste voorbeelden van het gevelpatroon volgen dit principe. Bij de implementatie ervan vertrouwen we meestal op voorwaardelijke verklaringen onder de motorkap, maar presenteren deze als een eenvoudige interface voor de gebruiker. Andere methoden die dit patroon implementeren, omvatten animeren () en css (). Kun je bedenken waarom ze een gevelpatroon zouden gebruiken??


Gedrag ontwerp patronen

Objectgeoriënteerde softwaresystemen hebben communicatie tussen objecten. Het niet organiseren van die communicatie kan leiden tot bugs die moeilijk te vinden en op te lossen zijn. Gedragsontwerppatronen schrijven verschillende methoden voor om de communicatie tussen objecten te organiseren. In dit gedeelte gaan we kijken naar de patronen van waarnemer en bemiddelaar.

Waarnemingspatroon

Het Observer-patroon is het eerste van de twee gedragspatronen die we zullen doormaken. Hier is wat het zegt:

In het waarnemerspatroon kan een onderwerp een lijst van waarnemers hebben die geïnteresseerd zijn in de levenscyclus. Telkens wanneer het onderwerp iets interessants doet, stuurt het een kennisgeving naar zijn waarnemers. Als een waarnemer niet langer geïnteresseerd is in het luisteren naar het onderwerp, kan het onderwerp het uit de lijst verwijderen.

Klinkt vrij eenvoudig, toch? We hebben drie methoden nodig om dit patroon te beschrijven:

  • publiceren (data): Geroepen door het onderwerp wanneer het een melding moet doen. Sommige gegevens kunnen door deze methode worden doorgegeven.
  • abonneren (waarnemer): Geroepen door het onderwerp om een ​​waarnemer aan zijn lijst van waarnemers toe te voegen.
  • unsubscribe (waarnemer): Geroepen door het onderwerp om een ​​waarnemer uit de lijst met waarnemers te verwijderen.

Nou, het blijkt dat de meeste moderne JavaScript-bibliotheken deze drie methoden ondersteunen als onderdeel van hun aangepaste evenementeninfrastructuur. Meestal is er een op() of attach () methode, a op gang brengen() of brand() methode, en een uit() of losmaken () methode. Beschouw het volgende fragment:

// We maken gewoon een koppeling tussen de jQuery-gebeurtenismethodes
// en die voorgeschreven door het Waarnemerspatroon, maar dat hoeft niet. var o = $ (); $ .subscribe = o.on.bind (o); $ .unsubscribe = o.off.bind (o); $ .publish = o.trigger.bind (o); // Usage document.on ('tweetsReceived', function (tweets) // voer een aantal acties uit en vuur dan een evenement $ .publish ('tweetsShow', tweets);); // We kunnen ons abonneren op dit evenement en dan ons eigen evenement starten. $ .subscribe ('tweetsShow', function () // toon de tweets op de een of andere manier ... // publiceer een actie nadat ze zijn getoond. $ .publish ('tweetsDisplayed);); $ .subscribe ('tweetsDisplayed, function () ...);

Het Observer-patroon is een van de eenvoudigere patronen om te implementeren, maar het is erg krachtig. JavaScript is zeer geschikt om dit patroon te gebruiken omdat het van nature op gebeurtenissen is gebaseerd. De volgende keer dat u webtoepassingen ontwikkelt, moet u nadenken over het ontwikkelen van modules die losjes met elkaar zijn gekoppeld en het Observer-patroon als een communicatiemiddel gebruiken. Het waarnemerspatroon kan problematisch worden als er te veel onderwerpen en waarnemers bij betrokken zijn. Dit kan gebeuren in grootschalige systemen en het volgende patroon dat we bekijken, probeert dit probleem op te lossen.

Bemiddelaar patroon

Het laatste patroon waar we naar gaan kijken is het middelaarspatroon. Het is vergelijkbaar met het Observer-patroon, maar met enkele opvallende verschillen.

Het bemiddelaarspatroon promoot het gebruik van een enkel gedeeld onderwerp dat de communicatie met meerdere objecten afhandelt. Alle objecten communiceren met elkaar via de bemiddelaar.

Een goede real-world analogie zou een Air Traffic Tower zijn, die de communicatie verzorgt tussen de luchthaven en de vluchten. In de wereld van softwareontwikkeling wordt het Mediator-patroon vaak gebruikt als een systeem te ingewikkeld wordt. Door bemiddelaars te plaatsen, kan communicatie worden afgehandeld door een enkel object in plaats van dat meerdere objecten met elkaar communiceren. In deze zin kan een bemiddelaarspatroon worden gebruikt om een ​​systeem te vervangen dat het waarnemerspatroon implementeert.

In deze essentie is er een vereenvoudigde implementatie van het bemiddelaarspatroon door Addy Osmani. Laten we het hebben over hoe u het kunt gebruiken. Stel je voor dat je een webapp hebt waarmee gebruikers op een album kunnen klikken en er muziek van kunnen afspelen. U zou een bemiddelaar als deze kunnen opzetten:

$ ('# album'). on ('klik', functie (e) e.preventDefault (); var albumId = $ (this) .id (); mediator.publish ("playAlbum", albumId);) ; var playAlbum = function (id) ... mediator.publish ("albumStartedPlaying", songList: [...], currentSong: "Without You"); ; var logAlbumPlayed = function (id) // Log het album in de backend; var updateUserInterface = function (album) // Update UI om weer te geven wat er wordt afgespeeld; // Mediator abonnementen mediator.subscribe ("playAlbum", playAlbum); mediator.subscribe ("playAlbum", logAlbumPlayed); mediator.subscribe ("albumStartedPlaying", updateUserInterface);

Het voordeel van dit patroon ten opzichte van het Observer-patroon is dat een enkel object verantwoordelijk is voor communicatie, terwijl in het waarnemerspatroon meerdere objecten kunnen luisteren en op elkaar kunnen abonneren..

In het Observer-patroon is er geen enkel object dat een beperking inkapselt. In plaats daarvan moeten de waarnemer en het onderwerp samenwerken om de beperking te handhaven. Communicatiepatronen worden bepaald door de manier waarop waarnemers en onderwerpen met elkaar zijn verbonden: een enkel onderwerp heeft meestal veel waarnemers en soms is de waarnemer van één onderwerp een onderwerp van een andere waarnemer.


Conclusie

Iemand heeft het in het verleden al met succes toegepast.

Het mooie van ontwerppatronen is dat iemand het in het verleden al met succes heeft toegepast. Er zijn veel open-source code die verschillende patronen in JavaScript implementeren. Als ontwikkelaars moeten we ons bewust zijn van welke patronen er zijn en wanneer ze moeten worden toegepast. Ik hoop dat deze tutorial je heeft geholpen om nog een stap verder te gaan in de richting van het beantwoorden van deze vragen.


Extra lezen

Veel van de inhoud van dit artikel is te vinden in het uitstekende boek 'Learning JavaScript Design Patterns', door Addy Osmani. Het is een online boek dat gratis is uitgegeven onder een Creative Commons-licentie. Het boek behandelt uitgebreid de theorie en implementatie van veel verschillende patronen, zowel in vanilla JavaScript als in verschillende JS-bibliotheken. Ik moedig je aan om ernaar te kijken als referentie wanneer je je volgende project start.