model-view-controller extjs anti-patterns

model view controller - ¿El MVC de Ext JS es un antipatrón?



model-view-controller extjs (6)

Actualmente estoy en Fast Track para ExtJS 4 de Sencha Training. Tengo una gran experiencia en ExtJS (desde ExtJS 2.0) y tenía mucha curiosidad por ver cómo se implementó el MVC en ExtJS 4.

Ahora, previamente, la forma en que simularía una especie de Controlador, sería delegar esa responsabilidad al Contenedor Principal. Imagine el siguiente ejemplo en ExtJS 3:

Ext.ns(''Test''); Test.MainPanel = Ext.extend(Ext.Container, { initComponent : function() { this.panel1 = new Test.Panel1({ listeners: { firstButtonPressed: function(){ this.panel2.addSomething(); }, scope: this } }); this.panel2 = new Test.Panel2(); this.items = [this.panel1,this.panel2]; Test.MainPanel.superclass.initComponent.call(this); } }); Test.Panel1 = Ext.extend(Ext.Panel, { initComponent : function() { this.addEvents(''firstButtonPressed''); this.tbar = new Ext.Toolbar({ items: [{ text: ''First Button'', handler: function(){ this.fireEvent(''firstButtonPressed''); } }] }); Text.Panel1.superclass.initComponent.call(this); } }); Test.Panel2 = Ext.extend(Ext.Panel, { initComponent : function() { this.items = [new Ext.form.Label(''test Label'')] Test.Panel2.superclass.initComponent.call(this); }, addSomething: function(){ alert(''add something reached'') } });

Como puede ver, mi MainPanel (además del hecho de que está sujetando ambos paneles) también delega eventos y crea así una comunicación entre los dos componentes, simulando así un tipo de Controlador.

En ExtJS 4 hay MVC directamente implementado en él. Lo que realmente me sorprendió fue que la forma en que el controlador realmente recupera los componentes es a través de QuerySelector, que en mi opinión es muy propenso a errores. Veamos:

Ext.define(''MyApp.controller.Earmarks'', { extend:''Ext.app.Controller'', views:[''earmark.Chart''], init:function () { this.control({ ''earmarkchart > toolbar > button'':{ click:this.onChartSelect }, ''earmarkchart tool[type=gear]'':{ click:this.selectChart } }); } });

Entonces, como podemos ver aquí, la forma en que el controlador es consciente del botón y la herramienta de la marca de selección es a través de selectores. Imaginemos ahora que estoy cambiando el diseño en mi earmarkchart y realmente muevo el botón fuera de la barra de herramientas. De repente, mi aplicación está rota, porque siempre tengo que ser consciente de que cambiar el diseño puede tener un impacto en el controlador asociado.

Se podría decir que puedo usar itemId en su lugar, pero de nuevo tengo que ser consciente de que si borro un componente tendré que dispersarlo para encontrar si hay alguna referencia oculta en mis Controladores para ese itemId, y también el hecho de que no puedo tienen el mismo ítem Id por componente primario, por lo que si tengo un Id. de elemento llamado ''ID de prueba'' en un Panel1 y el mismo en una Grilla1, aún necesitaría seleccionar si deseo la Id. de elemento del Panel 1 o de la Grilla1.

Entiendo que el Query es muy poderoso porque te da mucha flexibilidad, pero esa flexibilidad tiene un precio muy alto en mi opinión, y si tengo un equipo de 5 personas desarrollando interfaces de usuario y necesito explicar estos conceptos, pondré mis manos en el fuego que cometerán un montón de errores debido a los puntos a los que hice referencia anteriormente.

¿Cuál es su opinión general sobre esto? ¿Sería más fácil simplemente comunicarse de alguna manera con los eventos? Es decir, si mi controlador es realmente consciente de las vistas que espera de los eventos, entonces uno podría simplemente disparar un evento dosomethingController y el controlador asociado lo obtendría, en lugar de todo este problema de consulta.

Trabajo en un equipo de 25 desarrolladores. Usamos el patrón ExtJS MVC de Sencha. Pero creemos que su definición de MVC es engañosa. Tal vez podríamos llamar a su MVC un antipatrón también.

AMAIK, en el controlador MVC solo conoce el nombre o la ruta de la vista, y no tiene conocimiento de la estructura interna de la vista. Por ejemplo, no es responsabilidad del controlador, si la vista hace que la lista de clientes sea un simple menú desplegable o una autocompleta.

Sin embargo, en el MVC de Ext JS, el controlador debe conocer la representación de los elementos de la vista, ya que el controlador se engancha en esos elementos y escucha sus eventos. Esto significa que si un elemento de la vista cambia (por ejemplo, un botón se convierte en un enlace), el selector relevante en el controlador debería cambiar también. En otras palabras, el controlador está estrechamente acoplado a la estructura interna de la vista.

¿Es este motivo aceptable para denunciar el MVC de Ext JS como antipatrón? ¿Tenemos razón en que los controladores están acoplados a las vistas?


Creo que aquí hay un problema bastante malo: es muy difícil fragmentar unidades aisladas dentro de una página.

El enfoque con el que estoy experimentando (que también hace que sea más fácil escribir pruebas) es tener un objeto de contexto js vainilla para cada página que contiene la lógica (y tiene la ventaja de ser muy fácil de dividir y delegar en diferentes objetos ) Los controladores básicamente invocan métodos en los objetos de contexto cuando reciben eventos y tienen métodos para recuperar bits de la vista o realizar cambios en la vista.

Soy de un fondo wpf y me gusta pensar en los controladores como archivos de código subyacente. El diálogo entre presentador / contexto y vista es mucho más chattier que wpf (ya que no tiene binding + datatelating) pero no está mal.

También hay otro problema que aún no he tenido que resolver: el uso de singletons para controladores causa problemas para la reutilización de elementos de UI en la misma página. Eso parece un serio defecto de diseño. La solución común que he visto es (una vez más) apilar todo en un solo archivo y, en este caso, deshacerse del controlador por completo. Claramente, ese no es un buen enfoque tan pronto como las cosas comiencen a complicarse.

Sin embargo, parece que sacar todo el estado del controlador debería ayudar y entonces tendría un segundo nivel de objetos de contexto: el nivel superior básicamente asignaría un ID único a cada vista y tendría un mapa de context => view y proporcione el envío a los métodos de contexto individuales: básicamente sería una fachada. Entonces, el estado de cada vista se trataría en los objetos enviados a. ¡Un buen ejemplo de por qué la estática es malvada!


Creo que si usa el Arquitecto Sencha para producir las Vistas, entonces Herede de esa Vista para crear su propia Vista. Ahora esta Vista puede ser responsable de conectarse a cualquier evento y generar eventos significativos.

Esto es solo un pensamiento...

//Designer Generated Ext.define(''MyApp.view.MainView'', { extend: ''Ext.grid.GridPanel'', alias: ''widget.mainview'', initComponent: function() { } }); //Your View Decorator Ext.define(''MyApp.view.MainView'', { extend: ''MyApp.view.MainViewEx'', alias: ''widget.mainviewex'', initComponent: function() { this.mon(this, ''rowselect'', function(){ this.fireEvent(''userselected'', arguments); }, this); } });


Por supuesto, los controladores están vinculados a las vistas de alguna manera. Debe orientar exactamente qué elementos en sus vistas desea escuchar.

Por ejemplo: escuchar los clics de ese botón o cambiar ese elemento de formulario o a ese componente / evento personalizado.

El objetivo de MVC es el desacoplamiento y la reutilización de los componentes, y Sencha MVC es increíble para eso. Como dice @bmoeskau, debe tener cuidado al separar los controladores de vista (integrados para la vista / widgets) y los controladores de aplicación (manipulaciones de vistas de nivel superior) para aprovechar al máximo el patrón MVC. Y esto es algo no obvio cuando lees http://docs.sencha.com/ext-js/4-1/#!/guide/application_architecture . Refactorice su enfoque MVC, cree diferentes controladores, cree componentes personalizados y adopte la arquitectura ExtJS MVC completa para aprovecharlo.

Todavía hay un pequeño problema en el enfoque de Sencha en mi humilde opinión, el sistema MVC refs realmente no funciona cuando tienes múltiples instancias de las mismas vistas en una aplicación. Por ejemplo: si tiene un TabPanel con varias instancias del mismo Componente, el sistema de refs se rompe ya que siempre se enfocará en el primer elemento encontrado en el DOM ... Hay soluciones y un proyecto que intenta solucionarlo, pero espero que esto ser atendido pronto


Uso el MVC de ExtJS 4 todos los días. En lugar del código de spaghetti, tengo una elegante aplicación MVC que tiene una separación de connotaciones estrechamente definida y es ridículamente simple de mantener y extender. Tal vez su implementación debe ser modificada un poco para aprovechar al máximo lo que ofrece el enfoque MVC.


ACTUALIZACIÓN (marzo de 2015): Ext 5.0 introdujo ViewControllers que deberían abordar la mayoría de las inquietudes discutidas en este hilo. Ventajas:

  • Alcance mejor / impuesto sobre referencias de componentes dentro del ViewController
  • Es más fácil de encapsular la lógica específica de la vista por separado de la lógica de control de flujo de la aplicación
  • Ciclo de vida de ViewController administrado por el marco junto con la vista a la que está asociado

El Ext 5 aún ofrece la clase Ext.app.Controller existente, para mantener las cosas compatibles con versiones anteriores y para dar más flexibilidad sobre cómo estructurar su aplicación.

Respuesta original:

en el MVC de Ext JS, el controlador debe conocer la representación de los elementos de la vista, ya que el controlador se engancha en esos elementos y escucha sus eventos. Esto significa que si un elemento de la vista cambia (por ejemplo, un botón se convierte en un enlace), el selector relevante en el controlador debería cambiar también. En otras palabras, el controlador está estrechamente acoplado a la estructura interna de la vista.

De hecho, estoy de acuerdo en que en la mayoría de los casos esta no es la mejor opción por las razones exactas que citan, y es desafortunado que la mayoría de los ejemplos que vienen con Ext y Touch demuestren refs y funciones de control que a menudo se definen usando selectores que violan la encapsulación de la vista. Sin embargo, este no es un requisito de MVC: es solo cómo se han implementado los ejemplos y es fácil de evitar.

Por cierto, creo que definitivamente puede tener sentido diferenciar los estilos de controlador entre los controladores de aplicaciones reales (controlar el flujo de aplicaciones y la lógica de negocios compartida, deben estar totalmente desacoplados de las vistas, a esto es a lo que te refieres) y ver los controladores (control / lógica de eventos específica para una vista, estrechamente unida por diseño). Ejemplo de esto último sería la lógica para coordinar entre widgets dentro de una vista, totalmente internamente a esa vista. Este es un caso de uso común, y el acoplamiento de un controlador de vista a su vista no es un problema, es simplemente una estrategia de administración de código para mantener la clase de vista tan tonta como sea posible.

El problema es que en la mayoría de los ejemplos documentados, cada controlador simplemente hace referencia a lo que quiera, y ese no es un gran patrón. Sin embargo, este NO es un requisito de la implementación de MVC de Ext; es simplemente una convención (¿vago?) Que se usa en sus ejemplos. Es bastante simple (y sería aconsejable) que las clases de vista definan sus propios captadores y eventos personalizados para cualquier cosa que deba exponerse a los controladores de aplicaciones. La configuración de refs es solo una abreviatura: siempre puedes llamar a algo como myView.getSomeReference() y permitir que la vista dicte lo que se devuelve. En lugar de this.control(''some > view > widget'') simplemente defina un evento personalizado en la vista y haga esto this.control(''myevent'') cuando ese widget hace algo que el controlador necesita saber. Tan fácil como eso.

El inconveniente es que este enfoque requiere un poco más de código, y para casos simples (como ejemplos) puede ser excesivo. Pero estoy de acuerdo en que para las aplicaciones reales, o cualquier desarrollo compartido, es un enfoque mucho mejor.

Así que sí, vincular los controladores de nivel de aplicación a los controles de vista interna es, en sí mismo, un antipatrón. Pero el MVC de Ext no lo requiere, y es muy sencillo evitar hacerlo usted mismo.