design-patterns user-interface inheritance mvp composition

design patterns - Composición vs Herencia en MVP



design-patterns user-interface (5)

Estoy usando el patrón MVP para desarrollar una aplicación a gran escala. Mientras trabajaba en el desarrollo, me he planteado la cuestión de si se debe utilizar la composición o la herencia. Por ejemplo: Supongamos que tengo un formulario llamado Foo con los campos A y B. En la otra parte de la aplicación tengo una barra de formulario que tiene los mismos campos A y B pero un campo adicional C.

Actualmente, el código se escribe con el enfoque de herencia en donde la vista de la barra de formularios se hereda de la forma Foo . Los presentadores luego manejan los datos un poco diferente con el modelo. Esto funciona de manera bastante simple, pero me supera si sigue la regla de oro de "es A", ya que incluso cuando las formas son diferentes, manejan entradas comunes (A y B).

Sin embargo, aquí he estado pensando en la "composición sobre la herencia" y el Principio de Sustitución de Liskov y llegué a pensar que debería usar la composición en lugar de la herencia. Sin embargo, ya que estoy usando MVP, ha sido más complicado de lo esperado porque tendré que tener un presentador para la forma Foo con los campos A y B, luego un presentador para la barra con el campo C y una referencia al presentador de Foo para que puede inyectar los campos A y B en él.

El problema es que ha demostrado ser más código, ya que tendré que agregar algunos compositores y configuradores en el presentador de Foo para que pueda pasar los datos a Bar . Esto se siente de alguna manera como si estuviera rompiendo MVP para proporcionar composición.

Así que mis preguntas son:

¿Es realmente mejor para mi caso usar la composición sobre la herencia? ¿Por qué?

¿Usar la composición "break" MVP?


¿Es realmente mejor para mi caso usar la composición sobre la herencia? ¿Por qué?

Sí. Porque la composición es más confiable, más segura, más fácil de mantener, más visible, más documentable y más comprensible en aplicaciones más grandes. EN MI HUMILDE OPINIÓN. :)

¿Usar la composición "break" MVP?

Sí. Rompe el tipo de MVP simple que estás haciendo ahora. La composición le permite elegir cómo acoplar su código, y esto es muy bueno para aplicaciones más grandes. Usa más código porque tienes que ser específico acerca de cómo te estás uniendo.

Es muy razonable que una aplicación simple crezca y se convierta en un buen candidato para una transición de la simple herencia de MVP a una composición más sofisticada. Este es un paso de desacoplamiento que permite el nuevo acoplamiento de nuevas formas.

Esto es similar a la cantidad de aplicaciones web simples en transición para convertirse en aplicaciones basadas en API front / back. Esto es esencialmente un desacoplamiento de las vistas de usuario de front-end de los modelos de almacenamiento de back-end.


Cuando las formas son diferentes, manejan entradas comunes (A y B).

esto significa que el presentador de Foo es conceptualmente diferente del presentador de Bar y simplemente comparte algo de información común, por lo que no debe relacionarse por herencia. extraiga el código que maneja las entradas comunes en la clase de utilidad y reutilícelo en el presentador de Foo y en el de Bar.

en caso de que el concepto de Foo cambie, no afectará a Bar (y viceversa: si el concepto de Bar no puede cambiar sin cambiar también el concepto de Foo, entonces es "es A" la relación y la herencia podrían usarse de hecho)

En caso de duda, siempre prefiero la composición.


Por supuesto, cuando Foo no amplía la barra, es necesario agregar más código, ya que tiene captadores y configuradores adicionales. Pero el gran beneficio es que Foo ya no depende de Bar. Esto puede parecer un beneficio muy pequeño, pero imagínese cómo se vería si usara la ignición con más de 50 clases ... sería un infierno, sin lógica, y sería muy complicado si tuviera que cambiar un componente usado en una clase que se extiende por varias otras clases.

Por razones de mantenimiento, evite el uso de la herencia. Como dijiste, "un Bar no es un Foo", así que Bar no debería extender Foo. Por lo que he experimentado, la herencia nunca es una buena solución, y debería usarse solo para una familia de clases (cuando se usa un patrón compuesto, por ejemplo).


Una composición más limpia sería tener clases:

Modelos : A, B, C, Foo, Bar
Vistas : Vista, Vista, Vista, CV, FooView, BarView
Presentadores : APresentor, BPresentor, CPresentor, FooPresentor, BarPresentor

Donde FooView contiene una vista y una vista de BV, BarView contiene una vista, una vista de BV y una vista de CV y ​​los presentadores tienen una composición similar.

Esta composición hace que A, B y C (junto con sus vistas y presentadores) sean modulares, para que pueda mezclar y combinar a su gusto y las clases compuestas (Foo y Bar) se encarguen de la integración.

Esto podría usarse junto con la herencia: si Bar es un caso específico de Foo, entonces Bar debería heredar de Foor y BarPresentor puede heredar de FooPresentor. Sin embargo, consideraría la herencia de las vistas de una manera más por caso, ya que las vistas pueden o no ser adecuadas para la herencia, dependiendo de su comportamiento.


Comencemos con lo básico, lo más importante que debe saber sobre las clases, que una subclase es siempre una instancia completa de la superclase. Entonces, si define una variable de campo en una superclase, este campo siempre se crea si crea una instancia de la subclase. Puede usar super.getVariable () para obtener esa variable en la subclase para reutilizar un campo (variable de clase, campo, marca, que es lo mismo en la programación OO). Pero también puede llamar a subclassInstance.getVariable () desde el exterior y obtendrá el mismo campo (sin necesidad de cambiarlo por una subclase). Por lo tanto, a menudo no necesita llamar "súper" en su subclase, ya que generalmente solo desea obtener / establecer un campo de su superclase (¡incluidas las clases abstractas!) Desde el exterior. Ya que siempre debe establecer las variables de campo como privadas, siempre sugiero que nunca llame "super" para acceder a las variables de campo (porque incluso con super no puede acceder a los métodos / campo privados de su superclase ... en realidad uno de los mejores errores en Java, ya que no puede proporcionar la encapsulación completa de un árbol de clases hacia otras clases ... por lo que debe llamar a métodos generalmente protected como super.getField (), que es molesto pero necesario.

Ahora comencemos con los modelos de datos: usted tiene modelA y modelB. Podría heredarlos de una superclase, o simplemente de un objeto. Pero si solo hereda de Object, puede definir una interfaz simple (¡Java!) E implementar esta interfaz en modelA y modelB. Luego, maneja esas dos clases solo a través de la interfaz (ambos son "objetos de interfaz" y pueden manejarse de manera genérica). Si tiene el modelo C que consiste en el modelo A y el modelo B, simplemente usa una instancia (a veces también llamada "desreferencia") de ambos modelos dentro del modelo C, no se necesita más código. Así que puedes elegir estos modelos tan pequeños y simples como sea posible ("frijoles"). Una implementación basada en componentes, esa es la forma habitual de las estructuras de datos.

Si tiene GUI o formularios, se ve diferente. Probablemente tenga mucho código en común, y no desea dividir este código en docenas de clases diferentes y luego juntar los componentes en una clase de controlador / presentador. Por lo tanto, puede definir una clase abstracta, que contiene todos los campos / banderas compartidos y varios métodos para acceder y cambiarlos. Y luego, usted llama a estos métodos comunes desde el formulario A y el formulario B y reutiliza el código.

¿Te dice algo la teoría de conjuntos? Tienes dos círculos, A y B, y la intersección de A y B. La clase abstracta es la intersección, las subclases formA y formB son las diferencias establecidas. Aprender a codificar programas correctos ... es entender la teoría de conjuntos. ;-)

Para decirlo en sus palabras: la mayor parte del código de la forma Foo estará en una superclase abstracta Foobar , esta clase podrá manejar A y B. Luego, hereda la forma Foo y la barra desde ella, y mientras que C probablemente siga siendo un subconjunto de Foobar , agrega la habilidad en Bar para manejar C , esa es la diferencia establecida.

Al final, Bar no será Foo en ningún momento, ambos serán solo Foobar . ¿Tienes algunos nuevos campos / banderas compartidos? No hay problema, migra su código a Foobar , ¡y puede usarlo en ambas subclases!

Pero, ¿qué pasa si un día necesitas un tercer componente FooToo que es ligeramente diferente a Foo ? No hay problema, convierta a FooBarFoo en una clase abstracta, amplíe FooBar y luego cree Foo y FooToo como subclases. El resultado final será un árbol de clases en el que las raíces son (generalmente) clases abstractas, y las hojas son clases reales. Esta estructura proporciona una reutilización máxima del código (y no cambia los nombres de las clases, por lo que no es necesario cambiarlos). Cualquier otro código alreay usando la clase Foo ).

¿Dijo que implementará setter / getter en sus formularios (o su presentador)? Luego, también debe usar los modelos modelA y modelB (pero ningún modelo C, ya que C solo se usa en Bar y no Foo ). Estos modelos se utilizan como envoltorio para transportar datos entre Foo y Bar . Y este flujo de datos debe ser controlado por el presentador, no por Foo o Bar .

Entonces tu pregunta termina siendo esto: ¿De qué se trata un presentador? De hecho, el presentador es el código que ejecuta los componentes GUI y los modelos de datos. Es el "marco" que hace uso de los componentes de la GUI en una mano, y usa el getter / setter de los modelos de datos en la otra mano. Es el middleware entre las dos capas, la capa GUI y la capa de datos, e incluso entre diferentes componentes de GUI y diferentes modelos de datos.

Por lo general, solo hay dos formas de hacerlo: sin el presentador / controlador o con él. Sin, necesitarías copiar y pegar una gran cantidad de código de componente swing en tu clase de presentador. ¿Y qué? Sí, claro, cuando usas un componente swing, siempre usarás el patrón (M) VP, ¡no es posible hacerlo diferente!

Dicho esto, para crear un marco, debe utilizar un diseño de componentes, ya que desea proporcionar la máxima flexibilidad a un programador que trabaje con su marco. Pero un sistema productivo no es lo mismo que un marco, ese es el error que piensan muchos programadores de marcos. Entonces, si un programador de framework le dice "una implementación basada en componentes lo es todo", bueno, puede estar equivocado. Solo porque él está programando componentes para su marco, ¡esto no significa que tenga que hacer lo mismo para su presentador!

Entonces es cuando empezamos a hablar de los componentes de la GUI frente a la presentación de la GUI. Es posible crear "tantos componentes de presentador como sea posible", ya que podría tomar un sitio HTML simple y crear docenas de sitios PHP utilizando el método "include (...)". ¡Pero puedo asegurarle que el diseño basado en componentes no siempre mejora la capacidad de mantenimiento del código! Si puedo hacer algo con una sola clase, y puedo hacerlo de manera clara y legible, prefiero ir con una clase, no con diez. Un presentador = una clase, o para ser más específico: un marco / tab GUI = una clase.

Y nuevamente, si tienes 2 marcos / pestañas similares, pero no son lo mismo, ¿qué hacer? Migre el código compartido a una clase abstracta y cree las 2 subclases, ¿verdad? No, primero debes pensar en lo que comparten esos GUI. ¿Han compartido banderas? Así que mueva la bandera a una superclase abstracta. Pero, ¿se comportan de manera diferente? Bueno, solo implementas dos métodos diferentes dentro de la misma clase, y los llamas cuando los necesitas. Eso es lo más importante.

Para decirlo en sus palabras: El GUI Pres1 utiliza Foo , Bar , A , B y C en total. Y la GUI Pres2 solo está en otra clase si tiene indicadores diferentes. De lo contrario, establecerá una bandera Pres1 y una bandera Pres2 y marcará esta bandera dentro de sus métodos. Por if(flag="Pres1"){} else if(flag="Pres2"){} . Así, obtendrás un máximo de flexibilidad y reutilización de código.

No mire las clases de Java como algo no flexible, irreprochable, inalterable. Tan pronto como sea necesario, cambiará la estructura de su programa de manera intuitiva, si es un buen programador. No necesita pensar en conceptos artificiales, solo necesita comprender los patrones de programación orientados a objetos.

"Componente" siempre significa "algo con un constructor". Pero, a veces, simplemente usarás un método en lugar de un componente para hacer algo. Entonces, si alguien te dice "el diseño de componentes lo es todo", él te dice "el diseño basado en constructor es todo". ¡Pero para crear constructores, necesitas tener variables de campo / banderas! Sin las variables de campo, es completamente absurdo crear una nueva clase, solo por el bien de ella.

Atención: "Componente" significa no un método. Está claro que utilizará muchos métodos dentro de su (s) clase (s) de GUI para manejar las cosas muy fácilmente, por lo que al final solo tiene que llamar algunos métodos. ¡Así que no mezcle componentes y métodos! Siempre sugiero un diseño fuerte orientado a métodos, porque se trata de usar menos líneas de código. Así que define tantos métodos como puedas ... pero también cuantas menos clases / componentes puedas.