Tenemos una aplicación web donde tenemos muchos (> 50) pequeños componentes web que interactúan entre sí.
Para mantener todo desacoplado, tenemos como regla que ningún componente puede hacer referencia directa a otro. En cambio, los componentes activan eventos que luego se conectan (en la aplicación "principal") para llamar a los métodos de otro componente.
A medida que pasó el tiempo, más y más componentes se agregaron y el archivo de la aplicación "principal" se llenó de fragmentos de código que se ven así:
buttonsToolbar.addEventListener('request-toggle-contact-form-modal', () => {
contactForm.toggle()
})
buttonsToolbar.addEventListener('request-toggle-bug-reporter-modal', () => {
bugReporter.toggle()
})
// ... etc
Para mejorar esto, agrupamos una funcionalidad similar, en una Class
, asígnele un nombre relevante, pase los elementos participantes al crear instancias y maneje el "cableado" dentro de Class
, de la siguiente manera:
class Contact {
constructor(contactForm, bugReporter, buttonsToolbar) {
this.contactForm = contactForm
this.bugReporterForm = bugReporterForm
this.buttonsToolbar = buttonsToolbar
this.buttonsToolbar
.addEventListener('request-toggle-contact-form-modal', () => {
this.toggleContactForm()
})
this.buttonsToolbar
.addEventListener('request-toggle-bug-reporter-modal', () => {
this.toggleBugReporterForm()
})
}
toggleContactForm() {
this.contactForm.toggle()
}
toggleBugReporterForm() {
this.bugReporterForm.toggle()
}
}
e instanciamos así:
<html>
<contact-form></contact-form>
<bug-reporter></bug-reporter>
<script>
const contact = new Contact(
document.querySelector('contact-form'),
document.querySelector('bug-form')
)
</script>
</html>
Estoy realmente cansado de introducir patrones propios, especialmente los que no son realmente OOP-y ya que los estoy usando Classes
como simples contenedores de inicialización, por falta de una palabra mejor.
¿Existe un patrón definido mejor / más conocido para manejar este tipo de tareas que me falta?
Respuestas:
El código que tienes es bastante bueno. Lo que parece un poco desagradable es que el código de inicialización no es parte del objeto en sí. Es decir, puede crear una instancia de un objeto, pero si olvida llamar a su clase de cableado, es inútil.
Considere que un Centro de notificaciones (también conocido como Event Bus) definió algo como esto:
Este es un controlador de eventos de despacho múltiple de bricolaje. Entonces podría hacer su propio cableado simplemente requiriendo un NotificationCenter como argumento de constructor. Enviar mensajes a él y esperar a que pase sus cargas es el único contacto que tiene con el sistema, por lo que es muy SÓLIDO.
Nota: Utilicé literales de cadena in situ para que las claves sean consistentes con el estilo utilizado en la pregunta y por simplicidad. Esto no es aconsejable debido al riesgo de errores tipográficos. En su lugar, considere usar una enumeración o constantes de cadena.
En el código anterior, la barra de herramientas es responsable de informar al NotificationCenter en qué tipo de eventos está interesado y de publicar todas sus interacciones externas a través del método de notificación. Cualquier otra clase interesada en el
toolbar-button-click-event
simplemente se registraría en su constructor.Las variaciones interesantes en este patrón incluyen:
Las características interesantes incluyen:
Los trucos interesantes y los posibles remedios incluyen:
fuente
buttonsToolbar
sucede si quiero mover un componente, por ejemplo, a otro proyecto que no usa un bus de eventos?Solía introducir un "bus de eventos" de algún tipo, y en años posteriores comencé a confiar cada vez más en el Modelo de Objetos del Documento para comunicar eventos para el código de la interfaz de usuario.
En un navegador, el DOM es la única dependencia que siempre está presente, incluso durante la carga de la página. La clave es utilizar eventos personalizados en JavaScript y confiar en el burbujeo de eventos para comunicar esos eventos.
Antes de que la gente empiece a gritar sobre "esperar a que el documento esté listo" antes de adjuntar suscriptores, la
document.documentElement
propiedad hace referencia al<html>
elemento desde el momento en que JavaScript comienza a ejecutarse, sin importar dónde se importa el script o el orden en que aparece en su marcado.Aquí es donde puedes comenzar a escuchar eventos.
Es muy común tener un componente (o widget) de JavaScript en vivo dentro de una determinada etiqueta HTML en la página. El elemento "raíz" del componente es donde puede activar sus eventos burbujeantes. Los suscriptores del
<html>
elemento recibirán estas notificaciones al igual que cualquier otro evento generado por el usuario.Solo un ejemplo de código de placa de caldera:
Entonces el patrón se convierte en:
document.documentElement
propiedad (no es necesario esperar a que el documento esté listo)document.documentElement
.Esto debería funcionar para bases de código funcionales y orientadas a objetos.
fuente
Utilizo este mismo estilo con mi desarrollo de videojuegos con Unity 3D. Creo componentes como Salud, Entrada, Estadísticas, Sonido, etc. y los agrego a un Objeto de juego para construir lo que es ese objeto de juego. Unity ya tiene mecanismos para agregar componentes a los objetos del juego. Sin embargo, lo que encontré fue que casi todo el mundo consultaba componentes o hacía referencia directa a componentes dentro de otros componentes (incluso si usaban interfaces, todavía estaba más acoplado, gracias, preferí). Quería que los componentes se pudieran crear de forma aislada con cero dependencias de cualquier otro componente. Así que hice que los componentes dispararan eventos cuando los datos cambiaban (específicos del componente) y declaraba métodos para cambiar básicamente los datos. Luego, el objeto del juego creé una clase y pegué todos los eventos componentes a otros métodos componentes.
Lo que me gusta de esto es que para ver todas las interacciones de los componentes de un objeto de juego, puedo mirar esta clase 1. Parece que tu clase de contacto se parece mucho a mis clases de objetos de juego (nombro objetos del juego al objeto que deberían ser como MainPlayer, Orc, etc.).
Estas clases son una especie de clases de administrador. Ellos mismos realmente no tienen nada excepto instancias de componentes y el código para conectarlos. No estoy seguro de por qué creas métodos aquí que solo llaman a otros métodos componentes cuando puedes conectarlos directamente. El objetivo de esta clase es realmente organizar el evento enganchado.
Como nota al margen para mis registros de eventos, agregué una devolución de llamada de filtro y una devolución de llamada de argumentos. Cuando se activa el evento (hice mi propia clase de evento personalizado), llamará a la devolución de llamada de filtro si existe y si devuelve verdadero, pasará a la devolución de llamada de arg. El objetivo de la devolución de llamada del filtro era dar flexibilidad. Un evento puede desencadenarse por varias razones, pero solo quiero llamar a mi evento conectado si una verificación es verdadera. Un ejemplo podría ser un componente de entrada que tiene un evento OnKeyHit. Si tengo un componente de Movimiento que tiene métodos como MoveForward () MoveBackward (), etc., puedo conectar OnKeyHit + = MoveForward pero obviamente no me gustaría avanzar con ninguna tecla presionada. Solo me gustaría hacerlo si la clave fuera 'w'. Dado que OnKeyHit está completando argumentos para pasar y uno de esos es la clave que fue golpeada,
Para mí, la suscripción para una clase específica de administrador de objetos de juego se parece más a:
Como los componentes se pueden desarrollar de forma aislada, varios programadores podrían haberlos codificado. Con el ejemplo anterior, el codificador de entrada le dio al objeto de argumento una variable llamada Clave. Sin embargo, el desarrollador del componente Movimiento puede no haber usado Clave (si fuera necesario para mirar los argumentos, en este caso probablemente no, pero en otros usan los valores de argumento pasados). Para eliminar este requisito de comunicación, la devolución de llamada args actúa como un mapeo de los argumentos entre componentes. Entonces, la persona que crea esta clase de administrador de objetos de juego es la que necesita saber los nombres de las variables arg entre los 2 clientes cuando los conectan y realizan el mapeo en ese punto. Este método se llama después de la función de filtro.
Entonces, en la situación anterior, la persona de entrada nombró una variable dentro del objeto args 'Key' pero el Movimiento lo llamó 'keyPressed'. Esto ayuda a un mayor aislamiento entre los componentes a medida que se desarrollan y lo pone en el implementador de la clase de administrador para que se conecte correctamente.
fuente
Por lo que vale, estoy haciendo algo como parte de un proyecto de back-end y he tomado un enfoque similar:
Cómo manejé sus desafíos de construcción / cableado:
La analogía con tu barra de herramientas:
Problemas que puede enfrentar:
.filter
según el estado (en realidad, la implementación es más funcional: la conversación es un mapa plano de observables de un observable de eventos de cambio de estado).Entonces, en resumen, puede ver todo su dominio del problema como observables, o 'funcionalmente' / 'declarativamente' y considerar sus componentes web como flujos de eventos, como observables, derivados del bus (un observable), al cual el bus (un observador) también está suscrito. La creación de instancias de observables (por ejemplo, una nueva barra de herramientas) es declarativa, ya que todo el proceso puede verse como un observable de observables
.map
'd desde el flujo de entrada.fuente