paint-brush
¿Quieres dominar los patrones de diseño de Javascript? ¡Aquí está todo lo que necesita saber!por@alexmerced
4,189 lecturas
4,189 lecturas

¿Quieres dominar los patrones de diseño de Javascript? ¡Aquí está todo lo que necesita saber!

por Alex Merced41m2024/03/14
Read on Terminal Reader

Demasiado Largo; Para Leer

Descubra los entresijos de los patrones de diseño de JavaScript en esta guía completa. Ya sea un desarrollador principiante o experimentado, domine las técnicas para mejorar la estructura y la capacidad de mantenimiento de su código JavaScript.
featured image - ¿Quieres dominar los patrones de diseño de Javascript? ¡Aquí está todo lo que necesita saber!
Alex Merced HackerNoon profile picture
0-item


Cuando se trata de escribir código limpio, fácil de mantener y eficiente, los patrones de diseño desempeñan un papel crucial en el mundo del desarrollo de software. Los patrones de diseño son soluciones reutilizables a problemas comunes que enfrentan los desarrolladores al diseñar y construir sistemas de software. Proporcionan un enfoque estructurado para resolver desafíos específicos, lo que facilita la creación de código que no solo sea sólido sino también más fácil de entender y mantener.


En la programación orientada a objetos (OOP ), los patrones de diseño sirven como pautas para estructurar el código de una manera que promueva la flexibilidad, la reutilización y la escalabilidad. Encapsulan las mejores prácticas y principios de diseño que han evolucionado y se han sintetizado en soluciones probadas.


Descripción general del contenido

  • Categorías de patrones de diseño
  • Patrones de diseño comunes
  • Patrón singleton en JavaScript
  • Patrones de fábrica y fábrica abstracta en JavaScript
  • Patrón de construcción en JavaScript
  • Patrón de prototipo en JavaScript
  • Patrón de grupo de objetos en JavaScript
  • Patrón de adaptador en JavaScript
  • Patrón decorador en JavaScript
  • Patrón de proxy en JavaScript
  • Patrón compuesto en JavaScript
  • Patrón de puente en JavaScript
  • Patrón de peso mosca en JavaScript
  • Patrón de observador en JavaScript
  • Patrón de estrategia en JavaScript
  • Patrón de comando en JavaScript
  • Patrón de estado en JavaScript
  • Patrón de cadena de responsabilidad en JavaScript
  • Patrón de visitante en JavaScript
  • Conclusión


Categorías de patrones de diseño

Los patrones de diseño se pueden clasificar en tres grupos principales:

  1. Patrones de creación: estos patrones se centran en los mecanismos de creación de objetos, tratando de crear objetos de una manera adecuada a la situación. Abstraen el proceso de creación de instancias, haciéndolo más flexible e independiente del sistema.


  2. Patrones estructurales: Los patrones estructurales se ocupan de la composición de objetos, formando relaciones entre objetos para crear estructuras más grandes y complejas. Ayudan a definir cómo se pueden combinar objetos y clases para formar nuevas estructuras y proporcionar nuevas funciones.


  3. Patrones de comportamiento: Los patrones de comportamiento se ocupan de la comunicación entre objetos, definiendo cómo interactúan y distribuyen responsabilidades. Estos patrones le ayudan a diseñar sistemas en los que los objetos colaboran de una manera más flexible y eficiente.


Patrones de diseño comunes

Aquí hay una lista de algunos patrones de diseño comunes en cada categoría:


Patrones creacionales

  1. Patrón Singleton: garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global a esa instancia.
  2. Patrón de método de fábrica: define una interfaz para crear un objeto pero permite que las subclases alteren el tipo de objetos que se crearán.
  3. Patrón abstracto de fábrica: proporciona una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.
  4. Patrón Constructor: Separa la construcción de un objeto complejo de su representación, permitiendo que el mismo proceso de construcción cree diferentes representaciones.
  5. Patrón de prototipo: crea nuevos objetos copiando un objeto existente, conocido como prototipo.
  6. Patrón de grupo de objetos: gestiona un grupo de objetos reutilizables para minimizar la sobrecarga de creación y destrucción de objetos.


Patrones estructurales

  1. Patrón de adaptador: permite utilizar la interfaz de una clase existente como otra interfaz.
  2. Patrón decorador: asigna responsabilidades adicionales a un objeto de forma dinámica, proporcionando una alternativa flexible a la subclasificación.
  3. Patrón de proxy: proporciona un sustituto o marcador de posición para que otro objeto controle el acceso a él.
  4. Patrón compuesto: compone objetos en estructuras de árbol para representar jerarquías de parte y todo.
  5. Patrón puente: separa la abstracción de un objeto de su implementación, permitiendo que ambas varíen de forma independiente.
  6. Patrón Flyweight: minimiza el uso de memoria o los gastos computacionales al compartir tanto como sea posible con objetos relacionados.


Patrones de comportamiento

  1. Patrón de observador: define una dependencia de uno a muchos entre objetos, de modo que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.

  2. Patrón de estrategia: define una familia de algoritmos, encapsula cada uno y los hace intercambiables.

  3. Patrón de comando: encapsula una solicitud como un objeto, lo que permite la parametrización de clientes con colas, solicitudes y operaciones.

  4. Patrón de estado: permite que un objeto altere su comportamiento cuando cambia su estado interno, envolviendo el comportamiento en clases separadas.

  5. Patrón de cadena de responsabilidad: pasa la solicitud a lo largo de una cadena de controladores, lo que permite que cada controlador decida procesar la solicitud o pasarla al siguiente controlador de la cadena.

  6. Patrón de visitante: representa una operación que se realizará sobre los elementos de una estructura de objeto, lo que le permite definir nuevas operaciones sin cambiar las clases de los elementos.


En este blog, profundizaremos en cada uno de estos patrones de diseño, brindando explicaciones, casos de uso del mundo real y ejemplos de código JavaScript para ayudarlo a comprenderlos e implementarlos de manera efectiva en sus proyectos.


Patrón singleton en JavaScript

El patrón Singleton es un patrón de diseño creacional que garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global a esa instancia. Este patrón es especialmente útil cuando desea limitar la cantidad de instancias de una clase en su aplicación y controlar el acceso a una única instancia compartida.


En JavaScript, implementar el patrón Singleton es relativamente sencillo, gracias a la flexibilidad del lenguaje. Profundicemos en un ejemplo simple de cómo crear un Singleton en JavaScript.

Ejemplo de implementación

 // Singleton instance let instance = null;
 class Singleton { constructor() { if (!instance) { instance = this; // Your initialization code here } else { return instance; } } // Your methods and properties here }// Usage const singletonA = new Singleton(); const singletonB = new Singleton(); console.log(singletonA === singletonB); // Output: true (both variables reference the same instance)

En este ejemplo, creamos una clase Singleton con un constructor que verifica si ya existe una instancia. Si una instancia no existe, crea una y la asigna a la variable de instancia. Las llamadas posteriores al constructor devuelven la instancia existente, lo que garantiza que solo haya una instancia de la clase Singleton.


Casos de uso del mundo real

El patrón Singleton es útil en varios escenarios, que incluyen:


  • Administrar los ajustes de configuración: puede usar un Singleton para administrar los ajustes de configuración de su aplicación, asegurando que haya una única fuente de verdad para los valores de configuración.
  • Registrador y manejo de errores: se puede emplear un Singleton para mantener un mecanismo de registro o manejo de errores centralizado, lo que le permite consolidar entradas de registro o mensajes de error.
  • Conexiones de bases de datos: cuando trabaje con bases de datos, es posible que desee utilizar un Singleton para asegurarse de que solo haya una conexión de base de datos compartida en toda la aplicación para minimizar el consumo de recursos.
  • Almacenamiento en caché: la implementación de un Singleton para almacenar en caché los datos utilizados con frecuencia puede ayudar a optimizar el rendimiento al mantener una única instancia de caché.


Consideraciones

Si bien el patrón Singleton puede ser beneficioso, es esencial utilizarlo con prudencia. El uso excesivo del patrón Singleton puede generar un código estrechamente acoplado y un estado global, lo que puede hacer que su aplicación sea más difícil de mantener y probar. Por lo tanto, es crucial sopesar los pros y los contras y aplicar el patrón donde realmente agregue valor a su código base.


Patrones de fábrica y fábrica abstracta en JavaScript

El patrón Factory y el patrón Abstract Factory son patrones de diseño creacional que se ocupan de la creación de objetos, pero lo hacen de diferentes maneras y tienen distintos propósitos. Exploremos cada uno de estos patrones y veamos cómo se pueden implementar en JavaScript.


Patrón de fábrica

El patrón Factory es un patrón de creación que proporciona una interfaz para crear objetos pero permite que las subclases alteren el tipo de objetos que se crearán. Encapsula el proceso de creación de objetos, haciéndolo más flexible y desacoplado del código del cliente.

Ejemplo de implementación

 // Product class class Product { constructor(name) { this.name = name; } }
 // Factory for creating products class ProductFactory { createProduct(name) { return new Product(name); } }// Usage const factory = new ProductFactory(); const productA = factory.createProduct('Product A'); const productB = factory.createProduct('Product B');console.log(productA.name); // Output: 'Product A' console.log(productB.name); // Output: 'Product B'

En este ejemplo, ProductFactory es responsable de crear instancias de la clase Producto. Abstrae el proceso de creación, permitiéndole crear diferentes tipos de productos ampliando la fábrica.


Patrón abstracto de fábrica

El patrón Abstract Factory es otro patrón de creación que proporciona una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas. Le permite crear conjuntos de objetos que trabajan juntos armoniosamente.


Ejemplo de implementación

 // Abstract Product classes class Button { render() {} }
 class Checkbox { render() {} }// Concrete Product classes class MacButton extends Button { render() { return 'Render Mac button'; } }class MacCheckbox extends Checkbox { render() { return 'Render Mac checkbox'; } }class WindowsButton extends Button { render() { return 'Render Windows button'; } }class WindowsCheckbox extends Checkbox { render() { return 'Render Windows checkbox'; } }// Abstract Factory interface class GUIFactory { createButton() {} createCheckbox() {} }// Concrete Factories class MacFactory extends GUIFactory { createButton() { return new MacButton(); } createCheckbox() { return new MacCheckbox(); } }class WindowsFactory extends GUIFactory { createButton() { return new WindowsButton(); } createCheckbox() { return new WindowsCheckbox(); } }// Usage function createUI(factory) { const button = factory.createButton(); const checkbox = factory.createCheckbox(); return { button, checkbox }; }const macUI = createUI(new MacFactory()); console.log(macUI.button.render()); // Output: 'Render Mac button' console.log(macUI.checkbox.render()); // Output: 'Render Mac checkbox'const windowsUI = createUI(new WindowsFactory()); console.log(windowsUI.button.render()); // Output: 'Render Windows button' console.log(windowsUI.checkbox.render()); // Output: 'Render Windows checkbox'


En este ejemplo, tenemos dos fábricas concretas, MacFactory y WindowsFactory, cada una capaz de crear un conjunto de componentes de interfaz de usuario relacionados (botones y casillas de verificación) para sus respectivas plataformas. La función createUI le permite crear una interfaz de usuario coherente para una plataforma específica utilizando la fábrica adecuada.


Cuándo usar qué patrón:

  • Utilice Factory Pattern cuando desee encapsular el proceso de creación de objetos y proporcionar una interfaz simple para crear objetos con diferentes implementaciones.
  • Utilice el patrón Abstract Factory cuando necesite crear familias de objetos relacionados o dependientes que deban trabajar juntos. Ayuda a garantizar que los objetos creados sean compatibles y cohesivos.


Patrón de construcción en JavaScript

El Patrón Constructor es un patrón de diseño creacional que separa la construcción de un objeto complejo de su representación, permitiendo que el mismo proceso de construcción cree diferentes representaciones. Este patrón es especialmente útil cuando tiene un objeto con una gran cantidad de propiedades y desea simplificar la creación de instancias manteniendo la flexibilidad.

En JavaScript, el patrón de construcción a menudo se implementa utilizando una clase u objeto de construcción que guía la construcción paso a paso del objeto complejo. Profundicemos en un ejemplo para entender cómo funciona.


Ejemplo de implementación

 // Product class with multiple properties class Product { constructor() { this.name = ''; this.price = 0; this.color = 'white'; // ... other properties }
 // Additional methods can be defined here }// Builder for creating Product instances class ProductBuilder { constructor() { this.product = new Product(); } setName(name) { this.product.name = name; return this; // Return the builder for method chaining } setPrice(price) { this.product.price = price; return this; } setColor(color) { this.product.color = color; return this; } // Other methods to set additional properties build() { return this.product; // Return the fully constructed product } }// Usage const builder = new ProductBuilder();const productA = builder .setName('Product A') .setPrice(99.99) .setColor('blue') .build();const productB = builder .setName('Product B') .setPrice(49.99) .build();console.log(productA); console.log(productB);


En este ejemplo, tenemos una clase de Producto con múltiples propiedades. La clase ProductBuilder ayuda a crear instancias de Producto proporcionando métodos para configurar cada propiedad paso a paso. El encadenamiento de métodos le permite establecer múltiples propiedades de forma fluida y legible. Finalmente, el método de compilación devuelve la instancia del Producto completamente construida.


Casos de uso del mundo real

El patrón Builder es beneficioso en varios escenarios, que incluyen:

  • Creación de objetos complejos: cuando necesita crear objetos con muchas propiedades opcionales o configurables, el patrón Builder simplifica el proceso de construcción.
  • Objetos inmutables: los constructores se pueden utilizar para crear objetos inmutables, ya que puede establecer propiedades durante la construcción pero evitar modificaciones posteriores.
  • Constructores parametrizados: en lugar de utilizar largas listas de parámetros en los constructores, el patrón Builder proporciona un enfoque más limpio y organizado para construir objetos.
  • Objetos de configuración: al configurar bibliotecas o componentes, los constructores pueden ayudar a crear y personalizar objetos de configuración.


Consideraciones

Si bien el patrón Builder ofrece muchas ventajas, es importante tener en cuenta que agrega complejidad a su código base, especialmente si los objetos que se construyen son relativamente simples. Por lo tanto, es esencial evaluar si la complejidad introducida por Builder está justificada para su caso de uso específico.


Patrón de prototipo en JavaScript

El patrón prototipo es un patrón de diseño creacional que le permite crear nuevos objetos copiando un objeto existente, conocido como prototipo. Promueve la creación de objetos sin especificar la clase exacta de objeto a crear. Este patrón es particularmente útil cuando desea crear instancias de objetos complejos de manera eficiente.


En JavaScript, el patrón de prototipo está estrechamente relacionado con la propiedad prototype incorporada y el método Object.create() . Exploremos cómo implementar y usar el patrón prototipo en JavaScript.


Ejemplo de implementación

 // Prototype object const vehiclePrototype = { init(make, model) { this.make = make; this.model = model; }, getDetails() { return `${this.make} ${this.model}`; }, };
 // Create new instances using the prototype const car1 = Object.create(vehiclePrototype); car1.init('Toyota', 'Camry');const car2 = Object.create(vehiclePrototype); car2.init('Honda', 'Civic');console.log(car1.getDetails()); // Output: 'Toyota Camry' console.log(car2.getDetails()); // Output: 'Honda Civic'


En este ejemplo, definimos un objeto vehiclePrototype con métodos y propiedades comunes a todos los vehículos. Usamos Object.create() para crear nuevas instancias (car1 y car2) basadas en este prototipo. Estas instancias heredan las propiedades y métodos del prototipo, lo que le permite crear nuevos objetos con comportamiento compartido de manera eficiente.


Casos de uso del mundo real

El patrón prototipo es valioso en varios escenarios, que incluyen:

  • Reducción de los gastos generales de inicialización de objetos: cuando necesita crear varias instancias de un objeto con una estructura similar, el patrón de prototipo reduce la sobrecarga de configurar repetidamente las propiedades y métodos del objeto.
  • Clonación de objetos complejos: si tiene objetos complejos con estructuras anidadas, el patrón de prototipo simplifica la creación de objetos similares copiando el prototipo.
  • Creación de objetos configurables: cuando desee crear objetos con diferentes configuraciones, puede utilizar prototipos para inicializarlos con varias configuraciones.


Consideraciones

Si bien el patrón prototipo es útil, tiene algunas consideraciones:

  • Copia superficial: de forma predeterminada, el método Object.create() de JavaScript realiza una copia superficial de las propiedades. Si el prototipo contiene funciones o objetos anidados, se compartirán entre instancias. Es posible que deba implementar una copia profunda si es necesario.
  • Modificación del prototipo: tenga cuidado al modificar propiedades o métodos en el prototipo, ya que puede afectar todas las instancias creadas a partir de él.
  • Inicialización: el patrón prototipo a menudo requiere un paso de inicialización separado para establecer propiedades específicas de la instancia, lo que puede agregar complejidad.


Patrón de grupo de objetos en JavaScript

El patrón de grupo de objetos es un patrón de diseño creacional que gestiona un grupo de objetos reutilizables para minimizar la sobrecarga de creación y destrucción de objetos. Es especialmente útil cuando crear y destruir objetos es costoso o requiere muchos recursos. El patrón de grupo de objetos ayuda a mejorar el rendimiento y la utilización de recursos al reciclar y reutilizar objetos en lugar de crear otros nuevos desde cero.


En JavaScript, puede implementar el patrón de grupo de objetos utilizando matrices o clases de administración de grupos personalizados. Exploremos cómo funciona este patrón con un ejemplo simple.

Ejemplo de implementación

 class ObjectPool { constructor(maxSize) { this.maxSize = maxSize; this.pool = []; }
 create() { if (this.pool.length < this.maxSize) { // Create a new object and add it to the pool const obj = { /* Your object initialization code here */ }; this.pool.push(obj); return obj; } else { // Pool is full, cannot create more objects console.log('Pool is full. Cannot create more objects.'); return null; } } reuse() { if (this.pool.length > 0) { // Reuse an object from the pool return this.pool.pop(); } else { // Pool is empty, no objects available for reuse console.log('Pool is empty. No objects available for reuse.'); return null; } } release(obj) { // Release an object back to the pool for reuse this.pool.push(obj); } }// Usage const pool = new ObjectPool(5); // Create a pool with a maximum size of 5 objectsconst obj1 = pool.create(); const obj2 = pool.create(); const obj3 = pool.create();pool.release(obj2); // Release obj2 back to the pool for reuseconst obj4 = pool.reuse(); // Reuse an object from the pool (obj2)


En este ejemplo, creamos una clase ObjectPool que administra un grupo de objetos. El método de creación crea nuevos objetos cuando el grupo no está lleno, el método de reutilización recupera un objeto del grupo para reutilizarlo y el método de liberación devuelve un objeto al grupo para uso futuro.


Casos de uso del mundo real

El patrón de grupo de objetos es útil en varios escenarios, que incluyen:

  • Conexiones de bases de datos: la gestión de conexiones de bases de datos puede consumir muchos recursos. El uso de un grupo de objetos puede ayudar a reutilizar conexiones, mejorar el rendimiento y reducir la sobrecarga.
  • Gestión de subprocesos: en entornos de subprocesos múltiples, los grupos de objetos se pueden utilizar para gestionar subprocesos, especialmente cuando la creación de subprocesos es costosa.
  • Objetos que consumen muchos recursos: para los objetos que consumen una cantidad significativa de memoria o tardan en inicializarse, el patrón de grupo de objetos puede reducir la sobrecarga de crear y destruir instancias.


Consideraciones

Si bien el patrón de grupo de objetos ofrece beneficios de rendimiento, es importante considerar lo siguiente:

  • Gestión de recursos: es necesaria una gestión cuidadosa de los recursos para garantizar que los objetos se devuelvan correctamente al grupo después de su uso.
  • Tamaño del grupo: elegir un tamaño de grupo adecuado es crucial para equilibrar la utilización de recursos y el consumo de memoria.
  • Seguridad de subprocesos: en entornos de subprocesos múltiples, es necesario implementar mecanismos de sincronización adecuados para garantizar la seguridad de los subprocesos.


Patrón de adaptador en JavaScript

El patrón adaptador es un patrón de diseño estructural que permite que objetos con interfaces incompatibles trabajen juntos. Actúa como puente entre dos interfaces incompatibles, haciéndolas compatibles sin cambiar su código fuente. Este patrón es especialmente útil cuando necesita integrar o utilizar código existente que no se ajusta del todo a los requisitos de su aplicación.


En JavaScript, el patrón de adaptador se puede implementar utilizando clases o funciones que ajustan o adaptan la interfaz incompatible. Exploremos cómo implementar y usar el patrón de adaptador en JavaScript con un ejemplo práctico.


Ejemplo de implementación

Supongamos que tiene una clase existente llamada OldSystem con un método llamado legacyRequest :

 class OldSystem { legacyRequest() { return 'Data from the legacy system'; } }


Ahora desea utilizar este sistema heredado en su aplicación moderna que espera una interfaz diferente. Puede crear una clase de adaptador o una función como esta:

 class Adapter { constructor(oldSystem) { this.oldSystem = oldSystem; }
 newRequest() { const legacyData = this.oldSystem.legacyRequest(); // Adapt the data or perform any necessary transformations return `Adapted: ${legacyData}`; } }


Ahora, puede utilizar la clase Adaptador para hacer que el sistema heredado sea compatible con su aplicación moderna:

 const oldSystem = new OldSystem(); const adapter = new Adapter(oldSystem);
 const result = adapter.newRequest(); console.log(result); // Output: 'Adapted: Data from the legacy system'


En este ejemplo, la clase Adapter envuelve OldSystem y proporciona una nueva interfaz, newRequest, que es compatible con su aplicación moderna.


Casos de uso del mundo real

El patrón de adaptador es valioso en varios escenarios, que incluyen:

  • Integración de código heredado: cuando necesita integrar sistemas o bibliotecas heredados con una base de código moderna, los adaptadores pueden ayudar a cerrar la brecha entre los dos.
  • Bibliotecas de terceros: cuando se utilizan bibliotecas de terceros o API con interfaces incompatibles, los adaptadores pueden hacer que se ajusten a los requisitos de su aplicación.
  • Pruebas: los adaptadores pueden resultar útiles para crear objetos simulados o simular interfaces durante las pruebas para aislar dependencias.
  • Compatibilidad de versiones: se pueden utilizar adaptadores para mantener la compatibilidad con diferentes versiones de API o bibliotecas.


Consideraciones

Si bien el patrón de adaptador proporciona flexibilidad y compatibilidad, es esencial considerar algunos puntos:

  • Rendimiento: los adaptadores pueden introducir cierta sobrecarga debido a llamadas a métodos adicionales y transformaciones de datos. Mida y optimice según sea necesario.
  • Mantenibilidad: mantenga el código del adaptador limpio y bien documentado para garantizar que los futuros desarrolladores comprendan el propósito y el uso de los adaptadores.
  • Complejidad de la interfaz: tenga cuidado de no crear adaptadores demasiado complejos que intenten hacer demasiado. Mantenlos enfocados en una tarea de adaptación específica.


Patrón decorador en JavaScript

El patrón Decorador es un patrón de diseño estructural que le permite agregar nuevos comportamientos o responsabilidades a los objetos de forma dinámica sin alterar su código existente. Es una forma poderosa de ampliar la funcionalidad de los objetos envolviéndolos con objetos decorativos. Este patrón promueve el principio de "abierto a la extensión, pero cerrado a la modificación", lo que facilita agregar nuevas funciones a los objetos sin cambiar su implementación principal.


En JavaScript, el patrón Decorador se puede implementar mediante clases y composición de objetos. Exploremos cómo implementar y usar el patrón Decorador en JavaScript con un ejemplo práctico.


Ejemplo de implementación

Supongamos que tiene una clase base Coffee :

 class Coffee { cost() { return 5; // Base cost of a regular coffee } } Now, you want to add decorators to your coffee to customize it with additional options, such as milk and sugar:
 javascript Copy code class MilkDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 2; // Adding the cost of milk } }class SugarDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 1; // Adding the cost of sugar } }


Luego puedes crear instancias de café decoradas como esta:

 const regularCoffee = new Coffee(); const coffeeWithMilk = new MilkDecorator(regularCoffee); const coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
 console.log(regularCoffee.cost()); // Output: 5 console.log(coffeeWithMilk.cost()); // Output: 7 console.log(coffeeWithMilkAndSugar.cost()); // Output: 8


En este ejemplo, tenemos la clase Café que representa un café base. Las clases MilkDecorator y SugarDecorator son decoradores que envuelven un objeto de café y agregan el costo de la leche y el azúcar, respectivamente, al costo base.


Casos de uso del mundo real

El patrón decorador es valioso en varios escenarios, que incluyen:

  • Ampliación de clases: puede utilizar decoradores para agregar funciones a las clases sin modificar su código fuente, lo que facilita la introducción de nuevas funciones.
  • Composición dinámica: los decoradores permiten la composición dinámica de objetos en tiempo de ejecución, lo que le permite crear objetos complejos a partir de componentes simples.
  • Personalización: Puede personalizar objetos aplicando diferentes combinaciones de decoradores, brindando flexibilidad y configurabilidad.
  • Registro y creación de perfiles: los decoradores se pueden utilizar para registrar o perfilar llamadas a métodos sin modificar la clase original.


Consideraciones

Si bien el patrón decorador es versátil, es importante tener en cuenta algunas consideraciones:

  • Orden de decoración: el orden en el que aplica los decoradores puede afectar el comportamiento final, así que tenga en cuenta la secuencia en la que envuelve los objetos.
  • Complejidad: el uso excesivo de decoradores puede generar código complejo y complicado, así que considere cuidadosamente si los decoradores son la mejor solución para su caso de uso.
  • Compatibilidad de interfaz: asegúrese de que los decoradores se adhieran a una interfaz o contrato común para mantener la compatibilidad con los objetos que decoran.


Patrón de proxy en JavaScript

El patrón proxy es un patrón de diseño estructural que proporciona un sustituto o marcador de posición para que otro objeto controle el acceso a él. Actúa como intermediario o contenedor alrededor del objeto de destino, lo que le permite agregar comportamientos adicionales, controlar el acceso o retrasar la creación de objetos. El patrón de proxy es útil en varios escenarios, como la implementación de carga diferida, control de acceso y registro.


En JavaScript, los servidores proxy se pueden crear utilizando el objeto Proxy integrado. Exploremos cómo implementar y usar el patrón Proxy en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Carga diferida con proxy

Supongamos que tiene un objeto que consume muchos recursos y desea cargarlo de forma diferida solo cuando sea necesario. Puede utilizar un proxy para lograr una carga diferida:

 class ExpensiveResource { constructor() { console.log('Creating an expensive resource...'); }
 fetchData() { console.log('Fetching data...'); } }class LazyResourceProxy { constructor() { this.resource = null; } fetchData() { if (!this.resource) { this.resource = new ExpensiveResource(); } this.resource.fetchData(); } }// Usage const lazyResource = new LazyResourceProxy(); // The actual resource is created and data is fetched only when needed lazyResource.fetchData();

En este ejemplo, LazyResourceProxy actúa como sustituto de ExpensiveResource, creando el recurso real solo cuando se llama al método fetchData por primera vez.


Control de acceso con proxy

También puedes utilizar proxies para controlar el acceso a objetos y sus propiedades:

 const user = { username: 'john_doe', password: 'secret123', };
 const userProxy = new Proxy(user, { get(target, property) { if (property === 'password') { throw new Error('Access denied to password.'); } return target[property]; }, });console.log(userProxy.username); // Output: 'john_doe' console.log(userProxy.password); // Throws an error: 'Access denied to password.'

En este ejemplo, el proxy intercepta la operación de obtención y restringe el acceso a la propiedad de contraseña.


Casos de uso del mundo real

El patrón de proxy es valioso en varios escenarios, que incluyen:

  • Carga diferida: puede utilizar servidores proxy para posponer la creación e inicialización de objetos que consumen muchos recursos hasta que realmente sean necesarios.
  • Control de acceso: los servidores proxy pueden aplicar políticas de control de acceso, lo que le permite restringir u otorgar acceso a ciertas propiedades o métodos.
  • Almacenamiento en caché: los servidores proxy pueden implementar mecanismos de almacenamiento en caché para mejorar el rendimiento almacenando y devolviendo datos almacenados en caché en lugar de realizar operaciones costosas.
  • Registro y creación de perfiles: los servidores proxy pueden registrar o crear perfiles de llamadas a métodos, lo que le ayudará a obtener información sobre el comportamiento de los objetos.


Consideraciones

Al utilizar el patrón proxy, tenga en cuenta las siguientes consideraciones:

  • Gastos generales: los servidores proxy pueden introducir algunos gastos generales debido a la interceptación de operaciones. Tenga en cuenta las implicaciones para el rendimiento, especialmente en código crítico para el rendimiento.
  • Compatibilidad: asegúrese de que los servidores proxy se adhieran a la misma interfaz que los objetos de destino para mantener la compatibilidad con el código existente.
  • Seguridad: si bien los servidores proxy pueden ayudar a controlar el acceso, no se debe confiar en ellos como la única medida de seguridad. Es posible que sean necesarias medidas de seguridad adicionales, especialmente en el lado del servidor.


Patrón compuesto en JavaScript

El patrón compuesto es un patrón de diseño estructural que le permite componer objetos en estructuras en forma de árbol para representar jerarquías de parte y todo. Permite a los clientes tratar objetos individuales y composiciones de objetos de manera uniforme. El patrón compuesto es particularmente útil cuando necesita trabajar con estructuras complejas formadas por objetos más pequeños y relacionados, manteniendo al mismo tiempo una interfaz coherente.


En JavaScript, puede implementar el patrón compuesto utilizando clases u objetos que comparten una interfaz común, lo que le permite crear estructuras jerárquicas. Exploremos cómo implementar y usar el patrón compuesto en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Suponga que está creando una aplicación de diseño gráfico que necesita trabajar tanto con formas simples como con composiciones complejas de formas (por ejemplo, grupos). Puede utilizar el patrón compuesto para representar esta jerarquía:

 // Component interface class Graphic { draw() {} }
 // Leaf class (represents simple shapes) class Circle extends Graphic { constructor() { super(); // Circle-specific properties and methods } draw() { // Draw a circle } }// Composite class (represents groups of shapes) class Group extends Graphic { constructor() { super(); this.graphics = []; } add(graphic) { this.graphics.push(graphic); } draw() { // Draw each graphic in the group this.graphics.forEach((graphic) => graphic.draw()); } }// Usage const circle1 = new Circle(); const circle2 = new Circle(); const group = new Group();group.add(circle1); group.add(circle2);group.draw(); // Draws both circles in the group

En este ejemplo, la clase Gráfico sirve como interfaz del componente. La clase Círculo representa formas simples, mientras que la clase Grupo representa composiciones de formas. Tanto las clases Circle como Group implementan el método de dibujo, lo que le permite tratarlas de manera uniforme al renderizar.


Casos de uso del mundo real

El patrón compuesto es valioso en varios escenarios, entre ellos:

  • Marcos de gráficos y UI: se utiliza para representar interfaces de usuario complejas o escenas gráficas, donde es necesario tratar elementos individuales y grupos de elementos de manera consistente.
  • Sistemas de archivos: el patrón compuesto puede representar sistemas de archivos jerárquicos, donde archivos y directorios comparten una interfaz común.
  • Estructuras organizativas: se puede utilizar para modelar estructuras organizativas, como departamentos dentro de una empresa o divisiones dentro de una universidad.
  • Componentes anidados: cuando tiene componentes que pueden contener otros componentes (por ejemplo, un formulario que contiene campos de entrada), el patrón compuesto ayuda a administrar la estructura.


Consideraciones

Cuando trabaje con el patrón compuesto, considere lo siguiente:

  • Complejidad: si bien el patrón simplifica el trabajo con estructuras complejas, también puede introducir complejidad, especialmente cuando se trata de jerarquías profundas.
  • Interfaz uniforme: asegúrese de que todos los componentes (hojas y compuestos) compartan una interfaz común para mantener la coherencia.
  • Rendimiento: dependiendo de la implementación, atravesar una estructura compuesta puede tener implicaciones en el rendimiento, por lo que se debe optimizar según sea necesario.


Patrón de puente en JavaScript

El Patrón Puente es un patrón de diseño estructural que separa la abstracción de un objeto de su implementación. Le permite crear un puente entre los dos, permitiéndoles variar de forma independiente. Este patrón es particularmente útil cuando desea evitar un vínculo permanente entre una abstracción y su implementación, haciendo que su código sea más flexible y fácil de mantener.


En JavaScript, el patrón Bridge se puede implementar utilizando clases y objetos que proporcionan una interfaz abstracta para la abstracción y diferentes implementaciones concretas para diversas plataformas o características. Exploremos cómo implementar y usar el patrón Bridge en JavaScript con ejemplos prácticos.


Implementación de ejemplo

Suponga que está creando una aplicación de dibujo que puede representar formas en diferentes plataformas, como navegadores web y dispositivos móviles. Puede utilizar el patrón de puente para separar las formas de dibujo (abstracción) de la lógica de renderizado (implementación):

 // Abstraction class Shape { constructor(renderer) { this.renderer = renderer; }
 draw() { // Delegating the drawing to the specific renderer this.renderer.renderShape(this); } }// Implementor interface class Renderer { renderShape(shape) {} }// Concrete Implementors class WebRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on the web: ${shape.constructor.name}`); } }class MobileRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on mobile: ${shape.constructor.name}`); } }// Concrete Abstractions (Shapes) class Circle extends Shape { constructor(renderer) { super(renderer); } }class Square extends Shape { constructor(renderer) { super(renderer); } }// Usage const webRenderer = new WebRenderer(); const mobileRenderer = new MobileRenderer();const circle = new Circle(webRenderer); const square = new Square(mobileRenderer);circle.draw(); // Output: Drawing on the web: Circle square.draw(); // Output: Drawing on mobile: Square


En este ejemplo, la clase Shape representa la abstracción (formas que se dibujarán) y la clase Renderer representa la interfaz del implementador (lógica de representación específica de la plataforma). Diferentes implementadores concretos (WebRenderer y MobileRenderer) proporcionan lógica de renderizado para plataformas web y móviles, respectivamente. Las clases Círculo y Cuadrado son abstracciones concretas que representan formas.


Casos de uso del mundo real

El patrón puente es valioso en varios escenarios, que incluyen:

  • Independencia de la plataforma: cuando desea garantizar que la abstracción y la implementación puedan variar de forma independiente, lo que facilita el soporte de múltiples plataformas.
  • Controladores de bases de datos: se puede utilizar en controladores de bases de datos para separar el código específico de la base de datos (implementación) de las operaciones de la base de datos (abstracción).
  • Marcos GUI: en los marcos de interfaz gráfica de usuario (GUI), el patrón de puente puede ayudar a separar los elementos de la interfaz de usuario del sistema de ventanas subyacente.
  • Controladores de dispositivos: cuando se trata de controladores de dispositivos o hardware, este patrón le permite separar el código específico del dispositivo del código de aplicación de nivel superior.


Consideraciones

Cuando utilice el patrón Puente, considere lo siguiente:

  • Complejidad: si bien el patrón proporciona flexibilidad, puede aumentar la complejidad de su código base, especialmente cuando se trata de muchas abstracciones e implementaciones.
  • Mantenimiento: asegúrese de que las abstracciones y las implementaciones permanezcan sincronizadas a medida que se produzcan cambios, ya que pueden evolucionar de forma independiente.
  • Diseño de interfaz: diseñe cuidadosamente las interfaces de abstracción y de implementación para garantizar que cumplan con los requisitos de su aplicación.


Patrón de peso mosca en JavaScript

El patrón Flyweight es un patrón de diseño estructural que tiene como objetivo reducir el consumo de memoria y mejorar el rendimiento al compartir partes comunes de objetos. Lo logra separando el estado intrínseco de un objeto (compartido e inmutable) de su estado extrínseco (único y dependiente del contexto). Este patrón es particularmente útil cuando tiene una gran cantidad de objetos similares y desea minimizar el uso de memoria.


En JavaScript, puede implementar el patrón Flyweight utilizando clases u objetos para representar el estado intrínseco compartido y el estado extrínseco individual. Exploremos cómo implementar y usar el patrón Flyweight en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Suponga que está desarrollando un editor de texto que necesita mostrar una gran cantidad de texto. En lugar de crear un objeto separado para cada carácter, puede usar el patrón Flyweight para compartir objetos de caracteres cuando tienen las mismas propiedades intrínsecas (por ejemplo, fuente y tamaño):

 class Character { constructor(char, font, size) { this.char = char; this.font = font; this.size = size; }
 render() { console.log(`Rendering character "${this.char}" in ${this.font}, size ${this.size}`); } }class CharacterFactory { constructor() { this.characters = {}; } getCharacter(char, font, size) { const key = `${char}-${font}-${size}`; if (!this.characters[key]) { this.characters[key] = new Character(char, font, size); } return this.characters[key]; } }// Usage const factory = new CharacterFactory();const charA1 = factory.getCharacter('A', 'Arial', 12); const charA2 = factory.getCharacter('A', 'Arial', 12); const charB = factory.getCharacter('B', 'Times New Roman', 14);charA1.render(); // Output: Rendering character "A" in Arial, size 12 charA2.render(); // Output: Rendering character "A" in Arial, size 12 (shared instance) charB.render(); // Output: Rendering character "B" in Times New Roman, size 14

En este ejemplo, la clase Carácter representa caracteres individuales con propiedades intrínsecas como el carácter en sí, la fuente y el tamaño. La clase CharacterFactory garantiza que los caracteres con las mismas propiedades intrínsecas se compartan en lugar de duplicarse.


Casos de uso del mundo real

El patrón Flyweight es valioso en varios escenarios, que incluyen:

  • Procesamiento de texto: cuando se trabaja con grandes volúmenes de texto, puede reducir significativamente el consumo de memoria al compartir caracteres, fuentes u otras propiedades comunes relacionadas con el texto.
  • Desarrollo de juegos: en el desarrollo de juegos, se utiliza para optimizar la representación de objetos que comparten ciertas características, como texturas o materiales.
  • Interfaz de usuario (UI): se puede aplicar a componentes de UI con estilos, fuentes o íconos compartidos para minimizar el uso de recursos.
  • Almacenamiento en caché: el patrón se puede utilizar para almacenar en caché objetos o datos de uso frecuente para mejorar el rendimiento.


Consideraciones

Cuando utilice el patrón Flyweight, considere lo siguiente:

  • Identificación del estado intrínseco: identifique y separe cuidadosamente el estado intrínseco del estado extrínseco de los objetos. El estado intrínseco debe ser compartido, mientras que el estado extrínseco puede variar.
  • Seguridad de subprocesos: si su aplicación tiene subprocesos múltiples, asegúrese de que los objetos Flyweight sean seguros para subprocesos.
  • Memoria frente a rendimiento: si bien el patrón Flyweight reduce el uso de memoria, puede introducir una ligera sobrecarga de rendimiento debido a la necesidad de búsquedas e instancias compartidas.


Patrón de observador en JavaScript

El patrón de observador es un patrón de diseño de comportamiento que establece una dependencia de uno a muchos entre objetos. Permite que un objeto (el sujeto u observable) notifique a múltiples observadores (oyentes) sobre cambios en su estado o datos. Este patrón se usa comúnmente para implementar sistemas de manejo de eventos distribuidos, donde los cambios de estado de un objeto desencadenan acciones en otros objetos dependientes.

En JavaScript, puede implementar el patrón Observer utilizando clases personalizadas o funciones integradas como detectores de eventos y el método addEventListener . Exploremos cómo implementar y usar el patrón Observer en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Patrón de observador personalizado

Suponga que está creando una aplicación meteorológica y desea que se actualicen diferentes partes de la interfaz de usuario cuando cambian las condiciones meteorológicas. Puede utilizar una implementación personalizada del patrón Observer:

 class WeatherStation { constructor() { this.observers = []; }
 addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { const index = this.observers.indexOf(observer); if (index !== -1) { this.observers.splice(index, 1); } } notifyObservers() { this.observers.forEach((observer) => { observer.update(this); }); } setWeatherData(weatherData) { this.weatherData = weatherData; this.notifyObservers(); } }class WeatherDisplay { update(weatherStation) { console.log(`Current weather: ${weatherStation.weatherData}`); } }// Usage const weatherStation = new WeatherStation(); const display1 = new WeatherDisplay(); const display2 = new WeatherDisplay();weatherStation.addObserver(display1); weatherStation.addObserver(display2);weatherStation.setWeatherData('Sunny'); // Both displays update with the new weather data

En este ejemplo, la estación meteorológica actúa como sujeto que notifica a los observadores (objetos de visualización) cuando cambian los datos meteorológicos. Los observadores se suscriben al tema utilizando el método addObserver e implementan el método de actualización para reaccionar a los cambios.


Usar detectores de eventos

JavaScript también proporciona una forma integrada de implementar el patrón Observer utilizando detectores de eventos:

 class NewsPublisher { constructor() { this.subscribers = []; }
 subscribe(subscriber) { this.subscribers.push(subscriber); } unsubscribe(subscriber) { const index = this.subscribers.indexOf(subscriber); if (index !== -1) { this.subscribers.splice(index, 1); } } publishNews(news) { this.subscribers.forEach((subscriber) => { subscriber(news); }); } }// Usage const publisher = new NewsPublisher();const subscriber1 = (news) => { console.log(`Subscriber 1 received news: ${news}`); };const subscriber2 = (news) => { console.log(`Subscriber 2 received news: ${news}`); };publisher.subscribe(subscriber1); publisher.subscribe(subscriber2);publisher.publishNews('Breaking News: Important Announcement');

En este ejemplo, NewsPublisher actúa como asunto y los suscriptores (funciones) se agregan mediante el método de suscripción. El método publicarNoticias notifica a los suscriptores invocando sus funciones con las noticias.


Casos de uso del mundo real

El patrón de observador es valioso en varios escenarios, que incluyen:

  • Interfaces de usuario: implementación del manejo de eventos en interfaces gráficas de usuario (GUI) donde los componentes de la interfaz de usuario reaccionan a las acciones del usuario.
  • Sistemas de publicación-suscripción: creación de sistemas de publicación-suscripción para distribuir mensajes o eventos a múltiples suscriptores.
  • Model-View-Controller (MVC): Separar el modelo (datos) de la vista (UI) y notificar a las vistas sobre cambios en el modelo.
  • Manejo de eventos personalizados: creación de sistemas personalizados basados en eventos para gestionar cambios de estado e interacciones.

Consideraciones

Al utilizar el patrón de observador, considere lo siguiente:

  • Gestión de la memoria: tenga cuidado con las pérdidas de memoria cuando los observadores hacen referencias a sujetos. Garantizar la retirada adecuada de los observadores cuando ya no sean necesarios.
  • Orden de notificación: el orden en que se notifica a los observadores puede ser importante en algunos escenarios. Asegúrese de que el pedido cumpla con los requisitos de su aplicación.
  • Manejo de eventos: cuando utilice mecanismos de manejo de eventos integrados, tenga en cuenta la propagación de eventos y la propagación de eventos en el DOM, si corresponde.


Patrón de estrategia en JavaScript

El patrón de estrategia es un patrón de diseño de comportamiento que le permite definir una familia de algoritmos intercambiables, encapsular cada uno de ellos y hacerlos intercambiables. Permite a los clientes elegir dinámicamente el algoritmo apropiado en tiempo de ejecución. Este patrón promueve la flexibilidad y la reutilización al separar el comportamiento del algoritmo del contexto que lo utiliza.

En JavaScript, puede implementar el patrón de estrategia utilizando objetos o funciones para representar diferentes estrategias y un objeto de contexto que puede cambiar entre estas estrategias. Exploremos cómo implementar y usar el patrón de estrategia en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Suponga que está desarrollando una aplicación de comercio electrónico y desea calcular descuentos para diferentes tipos de clientes. Puede utilizar el patrón de estrategia para encapsular estrategias de descuento:

 // Discount Strategies const regularCustomerDiscount = (amount) => amount * 0.1; // 10% discount const premiumCustomerDiscount = (amount) => amount * 0.2; // 20% discount
 // Context class ShoppingCart { constructor(discountStrategy) { this.items = []; this.discountStrategy = discountStrategy; } addItem(item) { this.items.push(item); } calculateTotal() { const subtotal = this.items.reduce((total, item) => total + item.price, 0); return subtotal - this.discountStrategy(subtotal); } }// Usage const regularCustomerCart = new ShoppingCart(regularCustomerDiscount); const premiumCustomerCart = new ShoppingCart(premiumCustomerDiscount);regularCustomerCart.addItem({ name: 'Item 1', price: 50 }); premiumCustomerCart.addItem({ name: 'Item 2', price: 100 });console.log(`Regular Customer Total: $${regularCustomerCart.calculateTotal()}`); // Output: $45 (after 10% discount) console.log(`Premium Customer Total: $${premiumCustomerCart.calculateTotal()}`); // Output: $80 (after 20% discount)

En este ejemplo, definimos dos estrategias de descuento como funciones (regularCustomerDiscount y premiumCustomerDiscount). La clase ShoppingCart toma como parámetro una estrategia de descuento y calcula el precio total en función de la estrategia elegida.


Casos de uso del mundo real

El patrón estratégico es valioso en varios escenarios, que incluyen:

  • Selección de algoritmo: cuando necesita seleccionar un algoritmo de una familia de algoritmos de forma dinámica.
  • Configuración y Ajustes: Configurar una aplicación con diferentes opciones de comportamiento, como algoritmos de clasificación o estrategias de almacenamiento de datos.
  • Comportamiento personalizable: permitir a los usuarios personalizar y ampliar el comportamiento de una aplicación proporcionando diferentes estrategias.
  • Pruebas y simulacros: en las pruebas unitarias, puede utilizar el patrón de estrategia para proporcionar implementaciones simuladas de componentes para realizar pruebas.


Consideraciones

Al utilizar el patrón de estrategia, considere lo siguiente:

  • Separación clara: garantice una separación clara entre el contexto y las estrategias para mantener una base de código limpia y mantenible.
  • Cambio dinámico: la capacidad de cambiar dinámicamente entre estrategias es una característica clave de este patrón. Asegúrese de que su diseño admita esta flexibilidad.
  • Inicialización de estrategias: preste atención a cómo se inicializan las estrategias y se pasan al contexto para garantizar que se utilice la estrategia correcta.


Patrón de comando en JavaScript

El patrón de comando es un patrón de diseño de comportamiento que convierte una solicitud u operación simple en un objeto independiente. Le permite parametrizar objetos con diferentes solicitudes, retrasar o poner en cola la ejecución de una solicitud y admitir operaciones que se pueden deshacer. Este patrón desacopla al remitente de una solicitud de su receptor, lo que facilita la extensión y el mantenimiento del código.


En JavaScript, puede implementar el patrón de comando utilizando objetos o clases para representar comandos e invocadores que ejecutan esos comandos. Exploremos cómo implementar y usar el patrón de comando en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Suponga que está desarrollando una aplicación de control remoto para una casa inteligente y desea crear una forma flexible de controlar varios dispositivos.


Puedes usar el patrón de comando:

 // Command interface class Command { execute() {} }
 // Concrete Commands class LightOnCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOn(); } }class LightOffCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOff(); } }// Receiver (Device) class Light { turnOn() { console.log('Light is on.'); } turnOff() { console.log('Light is off.'); } }// Invoker (Remote Control) class RemoteControl { constructor() { this.commands = []; } addCommand(command) { this.commands.push(command); } executeCommands() { this.commands.forEach((command) => { command.execute(); }); } }// Usage const livingRoomLight = new Light(); const kitchenLight = new Light();const livingRoomLightOn = new LightOnCommand(livingRoomLight); const livingRoomLightOff = new LightOffCommand(livingRoomLight); const kitchenLightOn = new LightOnCommand(kitchenLight); const kitchenLightOff = new LightOffCommand(kitchenLight);const remoteControl = new RemoteControl();remoteControl.addCommand(livingRoomLightOn); remoteControl.addCommand(kitchenLightOff);remoteControl.executeCommands(); // Output: "Light is on." (for living room) // Output: "Light is off." (for kitchen)

En este ejemplo, el patrón de comando se utiliza para encapsular las acciones de encender y apagar luces. RemoteControl actúa como invocador y comandos concretos (por ejemplo, LightOnCommand y LightOffCommand) encapsulan las acciones que se ejecutarán.


Casos de uso del mundo real

El patrón de comando es valioso en varios escenarios, que incluyen:

  • Aplicaciones GUI: se usa comúnmente en interfaces gráficas de usuario (GUI) para implementar la funcionalidad de deshacer y rehacer, donde cada acción del usuario se encapsula como un comando.
  • Sistemas de control remoto: en aplicaciones de control remoto para dispositivos inteligentes, proporciona una forma flexible de controlar varios dispositivos con diferentes comandos.
  • Procesamiento por lotes: cuando necesita poner en cola y ejecutar una serie de solicitudes o tareas con diferentes parámetros o configuraciones.
  • Gestión de transacciones: en los sistemas de bases de datos, se puede utilizar para encapsular las operaciones de la base de datos como comandos, respaldando el comportamiento transaccional.


Consideraciones

Al utilizar el patrón de comando, considere lo siguiente:

  • Abstracción de comandos: asegúrese de que los comandos se abstraigan adecuadamente, encapsulando una sola acción u operación.
  • Deshacer y rehacer: si necesita la funcionalidad de deshacer y rehacer, implemente los mecanismos necesarios para admitir comandos de inversión.
  • Complejidad: tenga en cuenta la complejidad que se introduce al crear múltiples clases de comandos, especialmente en escenarios con una gran cantidad de comandos posibles.


Patrón de estado en JavaScript

El patrón de estado es un patrón de diseño de comportamiento que permite que un objeto cambie su comportamiento cuando cambia su estado interno. Encapsula estados como clases separadas y delega el comportamiento al objeto de estado actual. Este patrón ayuda a gestionar transiciones de estados complejas y promueve el principio de "abierto-cerrado", lo que facilita agregar nuevos estados sin modificar el código existente.


En JavaScript, puedes implementar el patrón de estado usando clases para representar estados y un objeto de contexto que delega su comportamiento al estado actual. Exploremos cómo implementar y usar el patrón de estado en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Suponga que está desarrollando una máquina expendedora que dispensa diferentes productos. El comportamiento de la máquina expendedora depende de su estado actual, como "Listo", "Dispensando" o "Agotado". Puede utilizar el patrón de estado para modelar este comportamiento:

 // State interface class VendingMachineState { insertMoney() {} ejectMoney() {} selectProduct() {} dispenseProduct() {} }
 // Concrete States class ReadyState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } insertMoney() { console.log('Money inserted.'); this.machine.setState(this.machine.getDispensingState()); } selectProduct() { console.log('Please insert money first.'); } }class DispensingState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } dispenseProduct() { console.log('Product dispensed.'); this.machine.setState(this.machine.getReadyState()); } }class VendingMachine { constructor() { this.readyState = new ReadyState(this); this.dispensingState = new DispensingState(this); this.currentState = this.readyState; } setState(state) { this.currentState = state; } getReadyState() { return this.readyState; } getDispensingState() { return this.dispensingState; } insertMoney() { this.currentState.insertMoney(); } selectProduct() { this.currentState.selectProduct(); } dispenseProduct() { this.currentState.dispenseProduct(); } }// Usage const vendingMachine = new VendingMachine();vendingMachine.selectProduct(); // Output: "Please insert money first." vendingMachine.insertMoney(); // Output: "Money inserted." vendingMachine.dispenseProduct(); // Output: "Product dispensed."

En este ejemplo, el patrón de estado se utiliza para gestionar el comportamiento de una máquina expendedora. Estados como "Listo" y "Dispensando" se representan como clases separadas y el contexto (máquina expendedora) delega su comportamiento al estado actual.


Casos de uso del mundo real

El patrón de estado es valioso en varios escenarios, que incluyen:

  • Gestión del flujo de trabajo: gestionar el flujo de trabajo de una aplicación con diferentes estados y transiciones, como el procesamiento de pedidos o los flujos de trabajo de aprobación.
  • Desarrollo del juego: implementar comportamientos de los personajes del juego que cambian según los estados del juego, como "inactivo", "atacando" o "defendiendo".
  • Interfaz de usuario (UI): Manejo del comportamiento de los componentes de la UI en función de diferentes interacciones del usuario o estados de la aplicación.
  • Máquinas de estados finitos: implementación de máquinas de estados finitos para análisis, validación o comunicación de red.


Consideraciones

Al utilizar el patrón de estado, considere lo siguiente:

  • Transiciones de estado: asegúrese de que las transiciones de estado estén bien definidas y que los estados encapsulen su comportamiento de manera efectiva.
  • Gestión del contexto: gestione las transiciones de estado del contexto y asegúrese de que delega el comportamiento correctamente al estado actual.
  • Complejidad: tenga en cuenta la complejidad que puede surgir al tratar con muchos estados y transiciones en una aplicación compleja.


Patrón de cadena de responsabilidad en JavaScript

El patrón de cadena de responsabilidad es un patrón de diseño de comportamiento que le ayuda a crear una cadena de objetos para gestionar una solicitud. Cada objeto de la cadena tiene la oportunidad de procesar la solicitud o pasarla al siguiente objeto de la cadena. Desacopla al remitente de una solicitud de sus receptores y permite que haya varios controladores en la cadena. Este patrón promueve la flexibilidad y la extensibilidad al permitirle agregar o modificar controladores sin afectar el código del cliente.


En JavaScript, puede implementar el patrón de cadena de responsabilidad utilizando objetos o clases que representan controladores y un cliente que inicia solicitudes. Cada controlador tiene una referencia al siguiente controlador de la cadena. Exploremos cómo implementar y utilizar el patrón de cadena de responsabilidad en JavaScript con ejemplos prácticos.


Implementación de ejemplo

Suponga que está desarrollando un sistema de procesamiento de pedidos y desea manejar los pedidos en función de su monto total. Puede utilizar el patrón de cadena de responsabilidad para crear una cadena de gestores, cada uno de los cuales es responsable de procesar pedidos dentro de un determinado rango de precios:

 // Handler interface class OrderHandler { constructor() { this.nextHandler = null; }
 setNextHandler(handler) { this.nextHandler = handler; } handleOrder(order) { if (this.canHandleOrder(order)) { this.processOrder(order); } else if (this.nextHandler) { this.nextHandler.handleOrder(order); } else { console.log('No handler can process this order.'); } } canHandleOrder(order) {} processOrder(order) {} }// Concrete Handlers class SmallOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 100; } processOrder(order) { console.log(`Processing small order for ${order.amount}`); } }class MediumOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 500; } processOrder(order) { console.log(`Processing medium order for ${order.amount}`); } }class LargeOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount > 500; } processOrder(order) { console.log(`Processing large order for ${order.amount}`); } }// Client class Order { constructor(amount) { this.amount = amount; } }// Usage const smallOrderHandler = new SmallOrderHandler(); const mediumOrderHandler = new MediumOrderHandler(); const largeOrderHandler = new LargeOrderHandler();smallOrderHandler.setNextHandler(mediumOrderHandler); mediumOrderHandler.setNextHandler(largeOrderHandler);const order1 = new Order(80); const order2 = new Order(250); const order3 = new Order(600);smallOrderHandler.handleOrder(order1); // Output: "Processing small order for 80" smallOrderHandler.handleOrder(order2); // Output: "Processing medium order for 250" smallOrderHandler.handleOrder(order3); // Output: "Processing large order for 600"

En este ejemplo, el patrón de cadena de responsabilidad se utiliza para manejar pedidos de diferentes montos. Los controladores como SmallOrderHandler, MediumOrderHandler y LargeOrderHandler determinan cada uno si pueden procesar un pedido en función del monto del pedido. Si pueden, lo tramitan; de lo contrario, pasan la orden al siguiente controlador de la cadena.


Casos de uso del mundo real

El patrón de cadena de responsabilidad es valioso en varios escenarios, entre ellos:

  • Manejo de solicitudes: administrar canales de procesamiento de solicitudes HTTP, donde cada middleware o controlador puede procesar o reenviar la solicitud.
  • Registro y manejo de errores: manejo de mensajes de registro o errores de forma estructurada, siendo cada controlador responsable de un tipo específico de mensaje de registro o condición de error.
  • Manejo de eventos: en sistemas controlados por eventos, puede usar este patrón para manejar eventos con múltiples suscriptores, donde cada suscriptor puede procesar o filtrar eventos.
  • Autorización y autenticación: implementar comprobaciones de autenticación y autorización en una secuencia, donde cada controlador verifica un aspecto específico de la solicitud.


Consideraciones

Al utilizar el patrón de cadena de responsabilidad, considere lo siguiente:

  • Configuración de la cadena: asegúrese de que la cadena esté configurada correctamente y que los manipuladores estén configurados en el orden correcto.
  • Responsabilidad del manejador: Cada manejador debe tener una responsabilidad clara y no debe superponerse con las responsabilidades de otros manejadores.
  • Manejo predeterminado: incluya lógica para los casos en los que ningún controlador en la cadena pueda procesar la solicitud.


Patrón de visitante en JavaScript

El patrón de visitante es un patrón de diseño de comportamiento que le permite separar un algoritmo de la estructura del objeto en el que opera. Proporciona una forma de agregar nuevas operaciones a objetos sin modificar sus clases, lo que facilita la ampliación de la funcionalidad para jerarquías de objetos complejas. Este patrón es especialmente útil cuando tienes un conjunto de elementos distintos y quieres realizar varias operaciones sobre ellos sin modificar su código.


En JavaScript, puede implementar el patrón de visitante utilizando funciones o clases para representar a los visitantes que visitan elementos dentro de una estructura de objeto. Exploremos cómo implementar y usar el patrón de visitante en JavaScript con ejemplos prácticos.


Ejemplo de implementación

Suponga que está desarrollando un sistema de gestión de contenidos en el que tiene diferentes tipos de elementos de contenido, como artículos, imágenes y vídeos. Desea realizar varias operaciones, como renderizar y exportar, en estos elementos sin modificar sus clases. Puedes utilizar el patrón de visitante:

 // Element interface class ContentElement { accept(visitor) {} }
 // Concrete Elements class Article extends ContentElement { accept(visitor) { visitor.visitArticle(this); } }class Image extends ContentElement { accept(visitor) { visitor.visitImage(this); } }class Video extends ContentElement { accept(visitor) { visitor.visitVideo(this); } }// Visitor interface class Visitor { visitArticle(article) {} visitImage(image) {} visitVideo(video) {} }// Concrete Visitors class RendererVisitor extends Visitor { visitArticle(article) { console.log(`Rendering article: ${article.title}`); } visitImage(image) { console.log(`Rendering image: ${image.caption}`); } visitVideo(video) { console.log(`Rendering video: ${video.title}`); } }class ExportVisitor extends Visitor { visitArticle(article) { console.log(`Exporting article: ${article.title}`); } visitImage(image) { console.log(`Exporting image: ${image.caption}`); } visitVideo(video) { console.log(`Exporting video: ${video.title}`); } }// Usage const elements = [new Article('Article 1'), new Image('Image 1'), new Video('Video 1')]; const renderer = new RendererVisitor(); const exporter = new ExportVisitor();elements.forEach((element) => { element.accept(renderer); element.accept(exporter); });

En este ejemplo, tenemos elementos de contenido como Artículo, Imagen y Video, y queremos realizar operaciones de renderizado y exportación en ellos sin modificar sus clases. Esto lo logramos implementando clases de visitantes como RendererVisitor y ExportVisitor que visitan los elementos y realizan las operaciones deseadas.


Casos de uso del mundo real

El patrón de visitante es valioso en varios escenarios, que incluyen:

  • Procesamiento de documentos: procesamiento de elementos en un documento, como HTML o XML, donde diferentes visitantes pueden realizar operaciones de análisis, representación o transformación.
  • Diseño de compiladores: en los compiladores, los visitantes pueden recorrer y analizar el árbol de sintaxis abstracta (AST) de un lenguaje de programación para diversos fines, como verificación de tipos, optimización y generación de código.
  • Estructuras de datos: cuando se trabaja con estructuras de datos complejas como árboles o gráficos, los visitantes pueden recorrer y manipular la estructura o el contenido de los datos.
  • Informes y análisis: en los sistemas de informes, los visitantes pueden generar informes, realizar análisis de datos o extraer información específica de un conjunto de datos.


Consideraciones

Al utilizar el patrón de visitante, considere lo siguiente:

  • Extensibilidad: el patrón facilita la adición de nuevas operaciones mediante la creación de nuevas clases de visitantes sin modificar los elementos existentes.
  • Complejidad: tenga en cuenta que el patrón puede introducir complejidad adicional, especialmente para estructuras de objetos simples.
  • Encapsulación: asegúrese de que los elementos encapsulen adecuadamente su estado y proporcionen acceso a través de métodos de visitante.


Conclusión

En esta exploración integral de los patrones de diseño en JavaScript, hemos profundizado en varios patrones que permiten a los desarrolladores crear código flexible, fácil de mantener y eficiente. Cada patrón de diseño aborda problemas específicos y proporciona soluciones elegantes a desafíos comunes de diseño de software.


Comenzamos por comprender el concepto fundamental de patrones de diseño y los categorizamos en tres grupos principales: patrones de creación, estructurales y de comportamiento. Dentro de cada categoría, examinamos patrones de diseño populares y mostramos sus implementaciones prácticas en JavaScript.


Aquí hay un breve resumen de los patrones de diseño clave que cubrimos:

  • Patrones de creación: estos patrones se centran en los mecanismos de creación de objetos, incluido el patrón Singleton para garantizar una única instancia de una clase, los patrones Factory y Abstract Factory para crear objetos con fábricas flexibles, el patrón Builder para construir objetos complejos paso a paso, el patrón Prototype para clonar. objetos y patrón de grupo de objetos para una reutilización eficiente de objetos.


  • Patrones estructurales: estos patrones se ocupan de la composición de objetos y proporcionan formas de construir estructuras complejas a partir de componentes más simples. Exploramos el Patrón Adaptador para adaptar interfaces, el Patrón Decorador para agregar comportamiento a objetos dinámicamente, el Patrón Proxy para controlar el acceso a objetos, el Patrón Compuesto para componer objetos en estructuras de árbol, el Patrón Puente para separar la abstracción de la implementación y el Patrón Flyweight. Patrón para minimizar el uso de memoria compartiendo un estado común.


  • Patrones de comportamiento: estos patrones se refieren a la interacción y comunicación entre objetos. Cubrimos el patrón de observador para implementar sistemas de manejo de eventos distribuidos, el patrón de estrategia para encapsular algoritmos intercambiables, el patrón de comando para convertir solicitudes en objetos independientes, el patrón de estado para administrar el comportamiento de los objetos basado en el estado interno, el patrón de cadena de responsabilidad para construir un cadena de controladores para procesar solicitudes y el patrón de visitante para separar algoritmos de estructuras de objetos.


Los patrones de diseño son herramientas valiosas en el conjunto de herramientas de un desarrollador, que permiten la creación de bases de código escalables y mantenibles. Comprender y aplicar estos patrones en sus proyectos de JavaScript le permite escribir software más eficiente, adaptable y robusto.


Recuerde que los patrones de diseño no son soluciones únicas para todos y su aplicabilidad depende de los requisitos y desafíos específicos de su proyecto. Considere cuidadosamente cuándo y cómo aplicarlos para lograr los mejores resultados.


A medida que siga creciendo como desarrollador de JavaScript, dominar estos patrones de diseño le permitirá afrontar complejos desafíos de diseño de software con confianza y creatividad. Ya sea que esté creando aplicaciones web, motores de juegos o cualquier otro software, los patrones de diseño serán sus aliados para crear código elegante y fácil de mantener. ¡Feliz codificación!


También publicado aquí .