Oggi, indosseremo i nostri cappelli informatici mentre apprendiamo alcuni modelli di progettazione comuni. I modelli di progettazione offrono agli sviluppatori i modi per risolvere i problemi tecnici in modo riutilizzabile ed elegante. Interessato a diventare uno sviluppatore JavaScript migliore? Quindi continua a leggere.
Tutorial ripubblicatoOgni poche settimane, rivisitiamo alcuni dei post preferiti del nostro lettore da tutta la cronologia del sito. Questo tutorial è stato pubblicato per la prima volta nel luglio 2012.
Modelli di progettazione solidi sono il mattone di base per le applicazioni software gestibili. Se hai mai partecipato a un colloquio tecnico, ti è stato chiesto su di loro. In questo tutorial, daremo un'occhiata ad alcuni modelli che è possibile iniziare a utilizzare oggi.
Un modello di progettazione è una soluzione software riutilizzabile
In poche parole, un modello di progettazione è una soluzione software riutilizzabile per un tipo specifico di problema che si verifica frequentemente durante lo sviluppo di software. Nel corso degli anni di pratica dello sviluppo del software, gli esperti hanno escogitato soluzioni per risolvere problemi simili. Queste soluzioni sono state incapsulate in schemi di progettazione. Così:
Entreremo in alcuni esempi di modelli di progettazione più avanti nel tutorial.
Nello sviluppo del software, i modelli di progettazione sono generalmente raggruppati in poche categorie. Tratteremo i tre più importanti in questo tutorial. Sono spiegati in breve qui sotto:
Potresti avere ancora delle domande dopo aver letto queste brevi descrizioni. Questo è naturale e le cose andranno a chiarire una volta esaminati alcuni modelli di progettazione in profondità. Quindi continua a leggere!
Quando leggi degli schemi di progettazione, vedrai spesso riferimenti a classi e oggetti. Questo può essere fonte di confusione, poiché JavaScript non ha realmente il costrutto di "classe"; un termine più corretto è "tipo di dati".
JavaScript è un linguaggio orientato agli oggetti in cui gli oggetti ereditano da altri oggetti in un concetto noto come eredità prototipica. Un tipo di dati può essere creato definendo ciò che è chiamato una funzione di costruzione, come questo:
function Person (config) this.name = config.name; this.age = config.age; Person.prototype.getAge = function () return this.age; ; var tilo = new Person (nome: "Tilo", età: 23); console.log (tilo.getAge ());
Notare l'uso del prototipo
quando si definiscono i metodi su Persona
tipo di dati. Dal momento che multiplo Persona
gli oggetti faranno riferimento allo stesso prototipo, questo consente getAge ()
metodo da condividere con tutte le istanze del Persona
tipo di dati, piuttosto che ridefinirlo per ogni istanza. Inoltre, qualsiasi tipo di dati che eredita da Persona
avrà accesso al getAge ()
metodo.
Un altro problema comune in JavaScript è che non esiste un vero senso delle variabili private. Tuttavia, possiamo usare chiusure per simulare in qualche modo la privacy. Considera il seguente frammento:
var retinaMacbook = (function () // Variabili private var RAM, addRAM; RAM = 4; // metodo privato addRAM = function (additionalRAM) RAM + = additionalRAM;; return // Variabili pubbliche e metodi USB: undefined , insertUSB: function (device) this.USB = device;, removeUSB: function () var device = this.USB; this.USB = non definito; return device;;) ();
Nell'esempio sopra, abbiamo creato a retinaMacbook
oggetto, con variabili e metodi pubblici e privati. Questo è come lo useremmo:
retinaMacbook.insertUSB ( "myUSB"); console.log (retinaMacbook.USB); // esegue il logout di "myUSB" console.log (retinaMacbook.RAM) // si disconnette non definito
C'è molto di più che possiamo fare con le funzioni e le chiusure in JavaScript, ma non ci occuperemo di tutto in questo tutorial. Con questa piccola lezione sui tipi di dati JavaScript e la privacy dietro di noi, possiamo portare avanti insieme le informazioni sui modelli di progettazione.
Ci sono molti diversi tipi di modelli di design creativi, ma ne illustreremo due in questo tutorial: Builder e Prototype. Trovo che questi siano usati abbastanza spesso da meritare l'attenzione.
Il Pattern Builder è spesso usato nello sviluppo web e probabilmente lo hai usato prima senza rendertene conto. In poche parole, questo modello può essere definito come segue:
L'applicazione del modello di builder ci consente di costruire oggetti specificando solo il tipo e il contenuto dell'oggetto. Non dobbiamo creare esplicitamente l'oggetto.
Ad esempio, probabilmente hai fatto questo innumerevoli volte in jQuery:
var myDiv = $ ('Questo è un div.'); // myDiv ora rappresenta un oggetto jQuery che fa riferimento a un nodo DOM. var someText = $ (''); // someText è un oggetto jQuery che fa riferimento a un parametro var HTMLParagraphElement = $ ('');
Dai uno sguardo ai tre esempi sopra. Nel primo, siamo passati in a elemento con alcuni contenuti. Nel secondo, siamo passati in un vuoto
etichetta. Nell'ultimo, siamo passati in un
elemento. Il risultato di tutti e tre erano gli stessi: ci è stato restituito un oggetto jQuery che fa riferimento a un nodo DOM.
Il $
variabile adotta il Pattern Builder in jQuery. In ogni esempio, ci è stato restituito un oggetto jQuery DOM e abbiamo avuto accesso a tutti i metodi forniti dalla libreria jQuery, ma in nessun momento abbiamo chiamato esplicitamente document.createElement
. La libreria JS gestiva tutto ciò sotto il cofano.
Immagina quanto lavoro sarebbe se dovessimo creare in modo esplicito l'elemento DOM e inserire il contenuto in esso! Sfruttando il modello di builder, siamo in grado di concentrarci sul tipo e sul contenuto dell'oggetto, piuttosto che sulla sua creazione esplicita.
In precedenza, abbiamo analizzato come definire i tipi di dati in JavaScript tramite le funzioni e aggiungendo metodi agli oggetti prototipo
. Il pattern Prototype consente agli oggetti di ereditare da altri oggetti, tramite i loro prototipi.
Il modello prototipo è un modello in cui gli oggetti vengono creati in base a un modello di un oggetto esistente tramite la clonazione.
Questo è un modo semplice e naturale per implementare l'ereditarietà in JavaScript. Per esempio:
var Person = numFeet: 2, numHeads: 1, numHands: 2; //Object.create prende il suo primo argomento e lo applica al prototipo del nuovo oggetto. var tilo = Object.create (Person); console.log (tilo.numHeads); // output 1 tilo.numHeads = 2; console.log (tilo.numHeads) // output 2
Le proprietà (e metodi) nel Persona
oggetto si applica al prototipo del TILO
oggetto. Possiamo ridefinire le proprietà sul TILO
oggetto se vogliamo che siano diversi.
Nell'esempio sopra, abbiamo usato Object.create ()
. Tuttavia, Internet Explorer 8 non supporta il metodo più recente. In questi casi, possiamo simularne il comportamento:
var vehiclePrototype = init: function (carModel) this.model = carModel; , getModel: function () console.log ("Il modello di questo veicolo è" + this.model); ; function vehicle (model) function F () ; F.prototype = vehiclePrototype; var f = new F (); f.init (modello); return f; var car = vehicle ("Ford Escort"); car.getModel ();
L'unico svantaggio di questo metodo è che non è possibile specificare proprietà di sola lettura, che possono essere specificate durante l'uso Object.create ()
. Tuttavia, il modello prototipo mostra come gli oggetti possono ereditare da altri oggetti.
I modelli di progettazione strutturale sono davvero utili per capire come dovrebbe funzionare un sistema. Permettono alle nostre applicazioni di scalare facilmente e rimangono manutenibili. Daremo un'occhiata ai seguenti modelli in questo gruppo: Composito e Facciata.
Il pattern composito è un altro pattern che probabilmente hai usato prima senza alcuna realizzazione.
Il modello composito dice che un gruppo di oggetti può essere trattato allo stesso modo di un singolo oggetto del gruppo.
Che cosa significa questo? Bene, considera questo esempio in jQuery (la maggior parte delle librerie JS avrà un equivalente a questo):
$ ( 'MyList ') addClass (' selezionato.'); . $ ( '# MyItem') addClass ( 'selezionato'); // non farlo su grandi tavoli, è solo un esempio. $ ("# dataTable tbody tr"). on ("click", function (event) alert ($ (this) .text ());); $ ('# myButton'). on ("click", function (event) alert ("Clicked."););
La maggior parte delle librerie JavaScript fornisce un'API coerente indipendentemente dal fatto che si tratti di un singolo elemento DOM o di un array di elementi DOM. Nel primo esempio, siamo in grado di aggiungere il selezionato
classe a tutti gli oggetti raccolti dal .la mia lista
selettore, ma possiamo usare lo stesso metodo quando si tratta di un elemento DOM singolare, #myItem
. Allo stesso modo, possiamo associare gestori di eventi usando il sopra()
metodo su più nodi o su un singolo nodo tramite la stessa API.
Sfruttando il pattern Composite, jQuery (e molte altre librerie) ci forniscono un'API semplificata.
Il Pattern Composito può a volte causare problemi. In un linguaggio scarsamente tipizzato come JavaScript, può spesso essere utile sapere se abbiamo a che fare con un singolo elemento o più elementi. Poiché il pattern composito utilizza la stessa API per entrambi, possiamo spesso scambiarci uno con l'altro e finire con errori imprevisti. Alcune librerie, come YUI3, offrono due metodi separati per ottenere elementi (Y.one ()
vs Y.all ()
).
Ecco un altro modello comune che diamo per scontato. In effetti, questo è uno dei miei preferiti perché è semplice, e ho visto che viene utilizzato in tutto il posto per aiutare con incoerenze del browser. Ecco di cosa tratta il modello di facciata:
The Facade Pattern fornisce all'utente un'interfaccia semplice, nascondendo al tempo stesso la complessità sottostante.
Il pattern Facade migliora quasi sempre l'usabilità di un software. Usando ancora jQuery come esempio, uno dei metodi più popolari della libreria è il pronto()
metodo:
$ (document) .ready (function () // tutto il tuo codice va qui ...);
Il pronto()
il metodo implementa effettivamente una facciata. Se dai un'occhiata alla fonte, ecco cosa trovi:
ready: (function () ... // Mozilla, Opera e Webkit if (document.addEventListener) document.addEventListener ("DOMContentLoaded", idempotent_fn, false); ... // IE Event model else if (document.attachEvent) // assicura l'attivazione prima dell'upload, forse tardi ma è sicuro anche per iframes document.attachEvent ("onreadystatechange", idempotent_fn); // Un fallback per window.onload, funzionerà sempre window.attachEvent ("onload", idempotent_fn); ...)
Sotto il cofano, il pronto()
il metodo non è così semplice. jQuery normalizza le incoerenze del browser per garantire che pronto()
viene licenziato al momento opportuno. Tuttavia, come sviluppatore, ti viene presentata un'interfaccia semplice.
La maggior parte degli esempi del modello di facciata segue questo principio. Quando ne implementiamo uno, solitamente facciamo affidamento su dichiarazioni condizionali, ma lo presentiamo come una semplice interfaccia per l'utente. Altri metodi che implementano questo modello includono animare()
e css ()
. Riesci a pensare al motivo per cui questi starebbero usando un modello di facciata?
Qualsiasi sistema software orientato agli oggetti avrà comunicazioni tra oggetti. Non organizzare questa comunicazione può portare a bachi difficili da trovare e risolvere. I modelli di progettazione comportamentale prescrivono diversi metodi di organizzazione della comunicazione tra gli oggetti. In questa sezione, esamineremo i modelli Observer e Mediator.
Il pattern Observer è il primo dei due modelli comportamentali che stiamo attraversando. Ecco cosa dice:
Nel modello di osservazione, un soggetto può avere un elenco di osservatori interessati al suo ciclo di vita. Ogni volta che il soggetto fa qualcosa di interessante, invia una notifica ai suoi osservatori. Se un osservatore non è più interessato ad ascoltare il soggetto, il soggetto può rimuoverlo dalla sua lista.
Sembra piuttosto semplice, giusto? Abbiamo bisogno di tre metodi per descrivere questo modello:
pubblicare (dati)
: Chiamato dall'oggetto quando ha una notifica da fare. Alcuni dati possono essere passati con questo metodo. iscriviti (osservatore)
: Chiamato dal soggetto per aggiungere un osservatore alla sua lista di osservatori. unsubscribe (osservatore)
: Chiamato dal soggetto per rimuovere un osservatore dalla sua lista di osservatori. Bene, si scopre che la maggior parte delle moderne librerie JavaScript supporta questi tre metodi come parte della loro infrastruttura di eventi personalizzati. Di solito c'è un sopra()
o attach ()
metodo, a trigger ()
o fuoco()
metodo, e un off ()
o staccare ()
metodo. Considera il seguente frammento:
// Creiamo solo un'associazione tra i metodi di eventi jQuery
// e quelli prescritti dal Pattern Observer ma non è necessario. var o = $ (); $ .subscribe = o.on.bind (o); $ .unsubscribe = o.off.bind (o); $ .publish = o.trigger.bind (o); // Usage document.on ('tweetsReceived', function (tweets) // esegue alcune azioni, quindi attiva un evento $ .publish ('tweetsShow', tweets);); // Possiamo iscriverci a questo evento e poi licenziare il nostro evento. $ .subscribe ('tweetsShow', function () // mostra i tweet in qualche modo ... // pubblica un'azione dopo che sono mostrati. $ .publish ('tweetsDisplayed);); $ .subscribe ('tweetsDisplayed, function () ...);
Il pattern Observer è uno dei modelli più semplici da implementare, ma è molto potente. JavaScript è adatto per adottare questo modello in quanto è naturalmente basato sugli eventi. La prossima volta che sviluppate applicazioni web, pensate allo sviluppo di moduli che sono liberamente accoppiati tra loro e adottano il modello Observer come mezzo di comunicazione. Il modello di osservatore può diventare problematico se ci sono troppi soggetti e osservatori coinvolti. Questo può accadere nei sistemi su larga scala e il modello successivo che cerchiamo di risolvere questo problema.
L'ultimo modello che vedremo è il modello di mediatore. È simile al pattern Observer ma presenta alcune differenze notevoli.
Il modello del mediatore promuove l'uso di un singolo soggetto condiviso che gestisce la comunicazione con più oggetti. Tutti gli oggetti comunicano tra loro attraverso il mediatore.
Una buona analogia con il mondo reale sarebbe una torre del traffico aereo, che gestisce le comunicazioni tra l'aeroporto e i voli. Nel mondo dello sviluppo del software, il modello Mediator è spesso usato come un sistema che diventa eccessivamente complicato. Posizionando i mediatori, la comunicazione può essere gestita attraverso un singolo oggetto, piuttosto che avere più oggetti che comunicano tra loro. In questo senso, un modello di mediatore può essere usato per sostituire un sistema che implementa il modello di osservatore.
C'è una implementazione semplificata del modello Mediator di Addy Osmani in questo senso. Parliamo di come potresti usarlo. Immagina di avere un'app Web che consente agli utenti di fare clic su un album e riprodurre musica da esso. Potresti creare un mediatore come questo:
$ ('# album'). on ('click', function (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) // Registra l'album nel backend; var updateUserInterface = function (album) // Aggiorna l'interfaccia utente per riflettere ciò che viene riprodotto; // Mediator subscriptions mediator.subscribe ("playAlbum", playAlbum); mediator.subscribe ("playAlbum", logAlbumPlayed); mediator.subscribe ("albumStartedPlaying", updateUserInterface);
Il vantaggio di questo modello rispetto al modello Observer è che un singolo oggetto è responsabile della comunicazione, mentre nel modello di osservatore, più oggetti potrebbero essere in ascolto e abbonarsi tra loro.
Nel pattern Observer, non esiste un singolo oggetto che incapsula un vincolo. Invece, l'osservatore e il soggetto devono cooperare per mantenere il vincolo. Gli schemi di comunicazione sono determinati dal modo in cui osservatori e soggetti sono interconnessi: un singolo soggetto di solito ha molti osservatori, e talvolta l'osservatore di un soggetto è soggetto di un altro osservatore.
Qualcuno l'ha già applicato con successo in passato.
Il bello dei modelli di design è che qualcuno lo ha già applicato con successo in passato. Ci sono molti codici open source che implementano vari pattern in JavaScript. Come sviluppatori, dobbiamo essere consapevoli di quali sono i modelli là fuori e quando applicarli. Spero che questo tutorial ti abbia aiutato a fare un ulteriore passo avanti nel rispondere a queste domande.
Gran parte del contenuto di questo articolo può essere trovato nell'eccellente libro di Learning JavaScript Design Patterns, di Addy Osmani. È un libro online che è stato rilasciato gratuitamente con una licenza Creative Commons. Il libro copre ampiamente la teoria e l'implementazione di molti modelli diversi, sia in JavaScript vanilla che in varie librerie JS. Ti incoraggio a considerarlo come riferimento quando inizi il tuo prossimo progetto.