Entendiendo los patrones de diseño en JavaScript

Hoy, nos pondremos nuestros sombreros de informática a medida que aprendamos sobre algunos patrones de diseño comunes. Los patrones de diseño ofrecen a los desarrolladores formas de resolver problemas técnicos de una manera reutilizable y elegante. ¿Interesado en convertirse en un mejor desarrollador de JavaScript? Entonces sigue leyendo.

Tutorial republicado

Cada pocas semanas, revisamos algunas de las publicaciones favoritas de nuestros lectores de toda la historia del sitio. Este tutorial se publicó por primera vez en julio de 2012..


Introducción

Los patrones de diseño sólidos son el bloque de construcción básico para las aplicaciones de software mantenibles. Si alguna vez has participado en una entrevista técnica, te ha gustado que te hayan preguntado sobre ellos. En este tutorial, veremos algunos patrones que puede comenzar a usar hoy..


¿Qué es un patrón de diseño??

Un patrón de diseño es una solución de software reutilizable.

En pocas palabras, un patrón de diseño es una solución de software reutilizable para un tipo específico de problema que ocurre con frecuencia al desarrollar software. A lo largo de los muchos años de práctica del desarrollo de software, los expertos han descubierto formas de resolver problemas similares. Estas soluciones han sido encapsuladas en patrones de diseño. Asi que:

  • Los patrones son soluciones probadas para problemas de desarrollo de software.
  • Los patrones son escalables, ya que generalmente están estructurados y tienen reglas que debe seguir.
  • Los patrones son reutilizables para problemas similares.

Entraremos en algunos ejemplos de patrones de diseño más adelante en el tutorial..


Tipos de patrones de diseño

En el desarrollo de software, los patrones de diseño generalmente se agrupan en unas pocas categorías. Vamos a cubrir los tres más importantes en este tutorial. A continuación se explican brevemente:

  1. Creacional Los patrones se centran en formas de crear objetos o clases. Esto puede sonar simple (y en algunos casos lo es), pero las aplicaciones grandes necesitan controlar el proceso de creación de objetos..
  2. Estructural los patrones de diseño se centran en las formas de administrar las relaciones entre los objetos para que su aplicación esté diseñada de forma escalable. Un aspecto clave de los patrones estructurales es garantizar que un cambio en una parte de su aplicación no afecte a todas las demás partes.
  3. De comportamiento Los patrones se centran en la comunicación entre objetos..

Aún puede tener preguntas después de leer estas breves descripciones. Esto es natural, y las cosas se aclararán una vez que veamos algunos patrones de diseño en profundidad a continuación. Así que sigue leyendo!


Una nota sobre las clases en JavaScript

Al leer sobre patrones de diseño, a menudo verá referencias a clases y objetos. Esto puede ser confuso, ya que JavaScript no tiene realmente la construcción de "clase"; un término más correcto es "tipo de datos".

Tipos de datos en JavaScript

JavaScript es un lenguaje orientado a objetos donde los objetos heredan de otros objetos en un concepto conocido como herencia prototípica. Se puede crear un tipo de datos definiendo lo que se llama una función constructora, como esta:

función Person (config) this.name = config.name; this.age = config.age;  Person.prototype.getAge = function () return this.age; ; var tilo = nueva Persona (nombre: "Tilo", edad: 23); console.log (tilo.getAge ());

Tenga en cuenta el uso de la prototipo al definir métodos en el Persona tipo de datos. Desde múltiples Persona Los objetos harán referencia al mismo prototipo, esto permite que el getAge () Método para ser compartido por todas las instancias de la Persona tipo de datos, en lugar de redefinirlo para cada instancia. Además, cualquier tipo de datos que herede de Persona Tendrá acceso a la getAge () método.

Tratar con la privacidad

Otro problema común en JavaScript es que no hay un verdadero sentido de las variables privadas. Sin embargo, podemos usar cierres para simular un poco la privacidad. Considere el siguiente fragmento de código:

var retinaMacbook = (function () // Variables privadas var RAM, addRAM; RAM = 4; // Método privado addRAM = function (additionalRAM) RAM + = additionalRAM;; return // Variables y métodos públicos USB: no definido , insertUSB: function (device) this.USB = device;, removeUSB: function () var device = this.USB; this.USB = undefined; dispositivo de retorno;;) ();

En el ejemplo anterior, creamos un retinaMacbook Objeto, con variables y métodos públicos y privados. Así es como lo usaríamos:

retinaMacbook.insertUSB ("myUSB"); console.log (retinaMacbook.USB); // cierra la sesión "myUSB" console.log (retinaMacbook.RAM) // cierra la sesión indefinido

Hay mucho más que podemos hacer con funciones y cierres en JavaScript, pero no veremos todo en este tutorial. Con esta pequeña lección sobre los tipos de datos de JavaScript y la privacidad detrás de nosotros, podemos continuar aprendiendo sobre los patrones de diseño..


Patrones de diseño creacional

Hay muchos tipos diferentes de patrones de diseño creativo, pero vamos a cubrir dos de ellos en este tutorial: Generador y Prototipo. Encuentro que estos se usan con la frecuencia suficiente para justificar la atención..

Patrón de constructor

El patrón del generador se usa a menudo en el desarrollo web, y probablemente lo haya usado antes sin darse cuenta. En pocas palabras, este patrón se puede definir de la siguiente manera:

La aplicación del patrón de construcción nos permite construir objetos especificando solo el tipo y el contenido del objeto. No tenemos que crear explícitamente el objeto..

Por ejemplo, probablemente has hecho esto innumerables veces en jQuery:

var myDiv = $ ('
Este es un div.
'); // myDiv ahora representa un objeto jQuery que hace referencia a un nodo DOM. var someText = $ ('

'); // someText es un objeto jQuery que hace referencia a un elemento HTMLParagraphElement var input = $ ('');

Echa un vistazo a los tres ejemplos anteriores. En la primera, pasamos en un

Elemento con algún contenido. En el segundo, pasamos en un vacío.

etiqueta. En la última, pasamos en un elemento. El resultado de los tres fue el mismo: se nos devolvió un objeto jQuery que hace referencia a un nodo DOM.

los PS La variable adopta el patrón Builder en jQuery. En cada ejemplo, nos devolvieron un objeto DOM de jQuery y tuvimos acceso a todos los métodos proporcionados por la biblioteca de jQuery, pero en ningún momento llamamos explícitamente document.createElement. La biblioteca de JS manejó todo eso bajo el capó.

¡Imagina cuánto trabajo sería si tuviéramos que crear explícitamente el elemento DOM e insertar contenido en él! Al aprovechar el patrón de construcción, podemos enfocarnos en el tipo y el contenido del objeto, en lugar de crearlo explícitamente..

Patrón prototipo

Anteriormente, analizamos cómo definir los tipos de datos en JavaScript a través de funciones y métodos de adición a los objetos. prototipo. El patrón de prototipo permite que los objetos hereden de otros objetos, a través de sus prototipos.

El patrón prototipo es un patrón donde los objetos se crean en base a una plantilla de un objeto existente a través de la clonación..

Esta es una forma fácil y natural de implementar la herencia en JavaScript. Por ejemplo:

var Person = numFeet: 2, numHeads: 1, numHands: 2; //Object.create toma su primer argumento y lo aplica al prototipo de su nuevo objeto. var tilo = Object.create (Persona); console.log (tilo.numHeads); // salidas 1 tilo.numHeads = 2; console.log (tilo.numHeads) // salidas 2

Las propiedades (y métodos) en el Persona objeto se aplica al prototipo de la tilo objeto. Podemos redefinir las propiedades en el tilo Objetar si queremos que sean diferentes..

En el ejemplo anterior, usamos Object.create (). Sin embargo, Internet Explorer 8 no admite el método más reciente. En estos casos, podemos simular su comportamiento:

var vehiclePrototype = init: function (carModel) this.model = carModel; , getModel: function () console.log ("El modelo de este vehículo es" + this.model); ; función vehículo (modelo) función F () ; F.prototipo = vehículoPrototipo; var f = nuevo F (); f.init (modelo); volver f;  var car = vehículo ("Ford Escort"); car.getModel ();

La única desventaja de este método es que no puede especificar propiedades de solo lectura, que se pueden especificar al usar Object.create (). No obstante, el patrón prototipo muestra cómo los objetos pueden heredar de otros objetos.


Patrones de diseño estructural

Los patrones de diseño estructural son realmente útiles para determinar cómo debería funcionar un sistema. Permiten a nuestras aplicaciones escalar fácilmente y seguir siendo mantenibles. Vamos a ver los siguientes patrones en este grupo: Compuesto y Fachada.

Patrón compuesto

El patrón compuesto es otro patrón que probablemente haya usado antes sin ninguna realización.

El patrón compuesto dice que un grupo de objetos se puede tratar de la misma manera que un objeto individual del grupo.

Entonces, ¿qué significa esto? Bueno, considere este ejemplo en jQuery (la mayoría de las bibliotecas JS tendrán un equivalente a esto):

$ ('. myList'). addClass ('selected'); $ ('# myItem'). addClass ('selected'); // No hagas esto en tablas grandes, es solo un ejemplo. $ ("# dataTable tbody tr"). en ("click", function (event) alert ($ (this) .text ());); $ ('# myButton'). on ("click", function (event) alert ("Clicked."););

La mayoría de las bibliotecas de JavaScript proporcionan una API consistente, independientemente de si estamos tratando con un solo elemento DOM o una matriz de elementos DOM. En el primer ejemplo, podemos añadir el seleccionado clase a todos los artículos recogidos por el .mi lista Selector, pero podemos usar el mismo método cuando se trata de un elemento DOM singular, #myItem. Del mismo modo, podemos adjuntar controladores de eventos utilizando el en() Método en varios nodos, o en un solo nodo a través de la misma API.

Al aprovechar el patrón compuesto, jQuery (y muchas otras bibliotecas) nos proporcionan una API simplificada.

El patrón compuesto a veces también puede causar problemas. En un lenguaje mal escrito, como JavaScript, a menudo puede ser útil saber si estamos tratando con un solo elemento o con varios elementos. Dado que el patrón compuesto utiliza la misma API para ambos, a menudo podemos confundir uno con el otro y terminar con errores inesperados. Algunas bibliotecas, como YUI3, ofrecen dos métodos separados para obtener elementos (Y.one () vs Y.all ()).

Patrón de fachada

Aquí hay otro patrón común que damos por sentado. De hecho, este es uno de mis favoritos porque es simple, y he visto que se usa en todas partes para ayudar con las inconsistencias del navegador. Esto es de lo que trata el patrón de fachada:

El Patrón de Fachada proporciona al usuario una interfaz simple, mientras oculta su complejidad subyacente.

El patrón de fachada casi siempre mejora la usabilidad de una pieza de software. Usando jQuery como ejemplo nuevamente, uno de los métodos más populares de la biblioteca es el Listo() método:

$ (documento) .ready (función () // todo su código va aquí ...);

los Listo() El método realmente implementa una fachada. Si echas un vistazo a la fuente, esto es lo que encuentras:

ready: (function () … // Mozilla, Opera, y Webkit if (document.addEventListener) document.addEventListener ("DOMContentLoaded", idempotent_fn, false);… // Modelo de evento de IE else if (document.attachEvent) // asegúrese de disparar antes de la carga; tal vez tarde pero seguro también para iframes document.attachEvent ("onreadystatechange", idempotent_fn); // Una alternativa a window.onload, que siempre funcionará window.attachEvent ("onload", idempotent_fn); ...)

Bajo el capó, el Listo() El método no es tan simple. jQuery normaliza las inconsistencias del navegador para asegurar que Listo() Se dispara a la hora apropiada. Sin embargo, como desarrollador, se te presenta una interfaz simple.

La mayoría de los ejemplos del patrón de fachada siguen este principio. Cuando implementamos uno, generalmente dependemos de sentencias condicionales bajo el capó, pero las presentamos como una interfaz simple para el usuario. Otros métodos que implementan este patrón incluyen animar() y css (). ¿Puedes pensar por qué estos estarían usando un patrón de fachada??


Patrones de diseño de comportamiento

Cualquier sistema de software orientado a objetos tendrá comunicación entre objetos. No organizar esa comunicación puede llevar a errores que son difíciles de encontrar y solucionar. Los patrones de diseño de comportamiento prescriben diferentes métodos para organizar la comunicación entre objetos. En esta sección, vamos a ver los patrones de observador y mediador..

Patrón de observador

El patrón Observer es el primero de los dos patrones de comportamiento que vamos a atravesar. Esto es lo que dice:

En el Patrón de observador, un sujeto puede tener una lista de observadores que estén interesados ​​en su ciclo de vida. Cada vez que el sujeto hace algo interesante, envía una notificación a sus observadores. Si un observador ya no está interesado en escuchar el tema, el sujeto puede eliminarlo de su lista.

Suena bastante simple, ¿verdad? Necesitamos tres métodos para describir este patrón:

  • publicar (datos): Llamado por el sujeto cuando tiene que hacer una notificación. Algunos datos pueden ser pasados ​​por este método.
  • suscribirse (observador): Llamado por el sujeto para agregar un observador a su lista de observadores.
  • darse de baja (observador): Llamado por el sujeto para eliminar un observador de su lista de observadores.

Bueno, resulta que la mayoría de las bibliotecas de JavaScript modernas admiten estos tres métodos como parte de su infraestructura de eventos personalizados. Por lo general, hay una en() o adjuntar() método, un desencadenar() o fuego() método, y un apagado() o despegar() método. Considere el siguiente fragmento de código:

// Simplemente creamos una asociación entre los métodos de eventos de jQuery.
// y los prescritos por el patrón de observador pero no tienes que hacerlo. var o = $ (); $ .subscribe = o.on.bind (o); $ .unsubscribe = o.off.bind (o); $ .publish = o.trigger.bind (o); // Uso document.on ('tweetsReceived', function (tweets) // realiza algunas acciones, luego activa un evento $ .publish ('tweetsShow', tweets);); // Podemos suscribirnos a este evento y luego lanzar nuestro propio evento. $ .subscribe ('TweetsShow', function () // mostrar los tweets de alguna manera ... // publicar una acción después de que se muestren. $ .publish ('tweetsDisplayed);); $ .subscribe ('tweetsDisplayed, function () …);

El patrón Observer es uno de los patrones más simples de implementar, pero es muy poderoso. JavaScript es muy adecuado para adoptar este patrón, ya que está naturalmente basado en eventos. La próxima vez que desarrolle aplicaciones web, piense en desarrollar módulos que se acoplen entre sí y adopte el patrón Observer como medio de comunicación. El patrón del observador puede volverse problemático si hay demasiados sujetos y observadores involucrados. Esto puede suceder en sistemas a gran escala, y el siguiente patrón que observamos trata de resolver este problema.

Patrón mediador

El último patrón que veremos es el patrón de mediador. Es similar al patrón Observer pero con algunas diferencias notables..

El patrón de mediador promueve el uso de un único sujeto compartido que maneja la comunicación con múltiples objetos. Todos los objetos se comunican entre ellos a través del mediador..

Una buena analogía del mundo real sería una Torre de Tráfico Aéreo, que maneja la comunicación entre el aeropuerto y los vuelos. En el mundo del desarrollo de software, el patrón de mediador se usa a menudo cuando un sistema se complica demasiado. Al colocar mediadores, la comunicación se puede manejar a través de un solo objeto, en lugar de tener múltiples objetos que se comunican entre sí. En este sentido, se puede usar un patrón de mediador para reemplazar un sistema que implementa el patrón de observador.

Hay una implementación simplificada del patrón de mediador por Addy Osmani en esta esencia. Vamos a hablar de cómo puedes usarlo. Imagina que tienes una aplicación web que permite a los usuarios hacer clic en un álbum y reproducir música desde él. Podrías configurar un mediador como este:

$ ('# 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) // Registrar el álbum en el backend; var updateUserInterface = function (album) // Actualizar UI para reflejar lo que se está reproduciendo; // Suscripciones de mediador mediator.subscribe ("playAlbum", playAlbum); mediator.subscribe ("playAlbum", logAlbumPlayed); mediator.subscribe ("albumStartedPlaying", updateUserInterface);

El beneficio de este patrón sobre el patrón Observer es que un solo objeto es responsable de la comunicación, mientras que en el patrón observador, múltiples objetos podrían estar escuchando y suscribiéndose entre sí..

En el patrón Observer, no hay un solo objeto que encapsule una restricción. En su lugar, el observador y el sujeto deben cooperar para mantener la restricción. Los patrones de comunicación están determinados por la forma en que los observadores y los sujetos están interconectados: un solo sujeto generalmente tiene muchos observadores, y algunas veces el observador de un sujeto es un sujeto de otro observador.


Conclusión

Alguien ya lo ha aplicado con éxito en el pasado..

Lo bueno de los patrones de diseño es que alguien ya lo ha aplicado con éxito en el pasado. Hay muchos códigos de código abierto que implementan varios patrones en JavaScript. Como desarrolladores, debemos ser conscientes de qué patrones hay y cuándo aplicarlos. Espero que este tutorial te haya ayudado a dar un paso más para responder estas preguntas..


Lectura adicional

Gran parte del contenido de este artículo se puede encontrar en el excelente libro Aprendizaje de patrones de diseño JavaScript, de Addy Osmani. Es un libro en línea que se publicó de forma gratuita bajo una licencia de Creative Commons. El libro cubre ampliamente la teoría y la implementación de muchos patrones diferentes, tanto en JavaScript de vainilla como en varias bibliotecas JS. Te animo a que lo veas como una referencia cuando comiences tu próximo proyecto..