Hay bastantes preguntas similares por ahí 1 ,2 ,3 ,4 , pero no parece exactamente el caso en esta pregunta, ni las soluciones parecen óptimas.
Esta es una pregunta general de OOP, suponiendo que el polimorfismo, los genéricos y los mixins estén disponibles. El lenguaje real que se utilizará es OOP Javascript (Typecript), pero es el mismo problema en Java o C ++.
Tengo jerarquías de clases paralelas, que a veces comparten el mismo comportamiento (interfaz e implementación), pero a veces cada uno tiene su propio comportamiento 'protegido'. Ilustrado así:
Esto es solo para fines ilustrativos ; No es el diagrama de clase real. Para leerlo:
- Cualquier cosa en la jerarquía común (centro) se comparte entre las jerarquías Canvas (izquierda) y SVG (derecha). Por compartir me refiero tanto a la interfaz como a la implementación.
- Cualquier cosa solo en las columnas izquierda o derecha significa un comportamiento (métodos y miembros) específico de esa jerarquía. Por ejemplo:
- Las jerarquías izquierda y derecha utilizan exactamente los mismos mecanismos de validación, que se muestran como un único método (
Viewee.validate()
) en la jerarquía común. - Solo la jerarquía del lienzo tiene un método
paint()
. Este método llama al método de pintura en todos los niños. - La jerarquía SVG necesita anular el
addChild()
método deComposite
, pero ese no es el caso con la jerarquía de lienzo.
- Las jerarquías izquierda y derecha utilizan exactamente los mismos mecanismos de validación, que se muestran como un único método (
- Las construcciones de las dos jerarquías laterales no se pueden mezclar. Una fábrica asegura eso.
Solución I - Herencia aparte
La herencia de Fowler's Tease Apart no parece hacer el trabajo aquí, porque hay alguna discrepancia entre los dos paralelos.
Solución II - Mixins
Este es el único en el que puedo pensar actualmente. Las dos jerarquías se desarrollan por separado, pero en cada nivel las clases se mezclan en la clase común, que no forman parte de una jerarquía de clases. Omitiendo el structural
tenedor, se verá así:
Tenga en cuenta que cada columna estará en su propio espacio de nombres, por lo que los nombres de clase no entrarán en conflicto.
La pregunta
¿Alguien puede ver fallas con este enfoque? ¿Alguien puede pensar en una mejor solución?
Apéndice
Aquí hay un código de muestra de cómo se va a usar. El espacio de nombres svg
se puede reemplazar con canvas
:
var iView = document.getElementById( 'view' ),
iKandinsky = new svg.Kandinsky(),
iEpigone = new svg.Epigone(),
iTonyBlair = new svg.TonyBlair( iView, iKandinsky ),
iLayer = new svg.Layer(),
iZoomer = new svg.Zoomer(),
iFace = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
iEyeL = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
iEyeR = new svg.Rectangle( new Rect( 60, 20, 20, 20) );
iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );
Esencialmente, en los clientes en tiempo de ejecución componen la jerarquía de instancias de las subclases de Viewee; al igual que:
Digamos que todos estos espectadores son de la jerarquía del lienzo, se representan atravesando la jerarquía que puede llamar paint()
a cada espectador. Si son de la jerarquía de svg, los espectadores saben cómo agregarse al DOM, pero no hay paint()
recorrido.
Respuestas:
El segundo enfoque segrega mejor las interfaces, siguiendo el principio de segregación de interfaz.
Sin embargo, agregaría una interfaz pintable.
También cambiaría algunos nombres. No es necesario crear confusión:
fuente
Eso en realidad no es cierto en absoluto. El mecanografiado tiene una ventaja sustancial sobre Java, es decir, la tipificación estructural. Puede hacer algo similar en C ++ con plantillas de tipo pato, pero es mucho más esfuerzo.
Básicamente, defina sus clases, pero no se moleste en extender o definir ninguna interfaz. Luego, simplemente defina la interfaz que necesita y tómela como parámetro. Luego, los objetos pueden coincidir con esa interfaz; no necesitan saber para extenderla de antemano. Cada función puede declarar exactamente y solo los bits que le importan, y el compilador le dará un pase si el tipo final lo cumple, a pesar de que las clases en realidad no extienden esas interfaces explícitamente.
Esto lo libera de la necesidad de definir realmente una jerarquía de interfaz y definir qué clases deberían extender qué interfaces.
Simplemente defina cada clase y olvídese de las interfaces: la tipificación estructural se encargará de ello.
Por ejemplo:
Esto es mecanografiado totalmente legítimo. Tenga en cuenta que las funciones de consumo no saben ni dan una sola mierda sobre las clases base o interfaces utilizadas en la definición de las clases.
No importa si las clases están relacionadas o no por herencia. No importa si extendieron su interfaz. Simplemente defina la interfaz como el parámetro, y ya está; todas las clases que lo cumplan serán aceptadas.
fuente
validate()
método (cuya implementación es idéntica para ambos); entonces solo svg.Viewee necesita anular addChild () , mientras que solo canvas.Viewee necesita paint () (que llama a paint () en todos los elementos secundarios , que son miembros de la clase Composite base ). Así que realmente no puedo visualizar esto con la tipificación estructural.SVGViewee.addChild()
, peroCanvasViewee
también necesita exactamente la misma función. Entonces, ¿parece tener sentido para mí que ambos sean inherentes a Composite?vista rápida
Solución 3: El patrón de diseño de software "Jerarquía de clases paralelas" es tu amigo.
Respuesta extendida larga
Su diseño COMENZÓ A LA DERECHA. Puede optimizarse, algunas clases o miembros pueden eliminarse, pero la idea de "jerarquía paralela" que está aplicando para resolver un problema ES CORRECTA.
Tratar con el mismo concepto varias veces, generalmente en jerarquías de control.
Después de un tiempo, TERMINÉ HACIENDO LA MISMA SOLUCIÓN QUE OTROS DESARROLLADORES, que a veces se denomina Patrón de diseño de "Jerarquía paralela" o Patrón de diseño de "Jerarquía dual".
(1) ¿Alguna vez ha dividido una sola clase en una sola jerarquía de clases?
(2) ¿Alguna vez ha dividido una sola clase en varias clases, sin una jerarquía?
Si ha aplicado estas soluciones anteriores, por separado, son una forma de resolver algunos problemas.
Pero, ¿qué pasa si combinamos estas dos soluciones, simultáneamente?
Combínalos y obtendrás este "Patrón de diseño".
Implementación
Ahora, apliquemos el patrón de diseño de software "Jerarquía de clases paralelas" a su caso.
Actualmente tiene 2 o más jerarquías de clases independientes, que son muy similares, tienen asociaciones o propósitos similares, tienen propiedades o métodos similares.
Desea evitar tener código o miembros duplicados ("coherencia"), sin embargo, no puede fusionar estas clases directamente en una sola, debido a las diferencias entre ellas.
Entonces, sus jerarquías son muy similares a esta figura, pero, sin embargo, hay más de una:
En este, aún no certificado, el Patrón de diseño, VARIAS JERARQUÍAS SIMILARES, SE FUSIONAN, EN UNA SOLA JERARQUÍA, y cada clase compartida o común se extiende por subclases.
Tenga en cuenta que esta solución es compleja, porque ya está lidiando con varias jerarquías, por lo tanto, es un escenario complejo.
1 La clase raíz
En cada jerarquía hay una clase "raíz" compartida.
En su caso, hay una clase "Compuesta" independiente, para cada jerarquía, que puede tener algunas propiedades similares y algunos métodos similares.
Algunos de esos miembros pueden fusionarse, algunos de esos miembros no pueden fusionarse.
Entonces, lo que puede hacer un desarrollador es crear una clase raíz base y subclasificar el caso equivalente para cada jerarquía.
En la Figura 2, puede ver un diagrama solo para esta clase, en el que cada clase mantiene su espacio de nombres.
Los miembros ya están omitidos.
Como puede observar, cada clase "Compuesta" ya no se encuentra en una jerarquía separada, sino que se fusiona en una sola jerarquía compartida o común.
Luego, agreguemos los miembros, los que son iguales, se pueden mover a la superclase, y los que son diferentes, a cada clase base.
Y como ya sabe, los métodos "virtuales" o "sobrecargados" se definen en la clase base, pero se reemplazan en las subclases. Como la figura 3.
Tenga en cuenta que tal vez hay algunas clases sin miembros y que puede verse tentado a eliminar esas clases, NO. Se llaman "clases huecas", "clases enumerativas" y otros nombres.
2 Las subclases
Volvamos al primer diagrama. Cada clase "Compuesta" tenía una subclase "Viewee" en cada jerarquía.
El proceso se repite para cada clase. Tenga en cuenta que en la Figura 4, la clase "Common :: Viewee" desciende de "Common :: Composite", pero, por simplicidad, la clase "Common :: Composite" se omite del diagrama.
Notarás que "Canvas :: Viewee" y "SVG :: Viewee", NO YA NO descienden de su respectivo "Composite", sino del común "Common :: Viewee".
Puede agregar los miembros, ahora.
3 Repita el proceso
El proceso continuará, para cada clase, "Canvas :: Visual" no descenderá de "Canvas :: Viewee", pero desde "Commons :: Visual", "Canvas :: Structural" no descenderá de "Canvas :: Viewee ", pero desde" Commons :: Structural ", y así sucesivamente.
4 El diagrama de jerarquía 3D
Terminará de obtener una especie de diagrama 3D, con varias capas, la capa superior, tiene la jerarquía "Común" y las capas inferiores, tiene cada jerarquía adicional.
Sus jerarquías de clases independientes originales, donde algo similar a esto (Figura 6):
Tenga en cuenta que algunas clases se omiten, y toda la jerarquía "Canvas" se omite, por simplicidad.
La jerarquía de clase integrada final puede ser algo similar a esto:
Tenga en cuenta que algunas clases se omiten, y todas las clases "Canvas" se omiten, por simplicidad, pero serán similares a las clases "SVG".
Las clases "comunes" podrían representarse como una sola capa de un diagrama 3D, las clases "SVG" en otra capa y las clases "lienzo", en una tercera capa.
Verifique que cada capa esté relacionada con la primera, en la que cada clase tiene una clase padre de la jerarquía "Común".
La implementación del código puede requerir el uso de herencia de interfaz, herencia de clase o "mixins", dependiendo de lo que admita su lenguaje de programación.
Resumen
Como cualquier solución de programación, no se apresure a la optimización, la optimización es muy importante, sin embargo, una mala optimización puede convertirse en un problema mayor que el problema original.
No recomiendo aplicar "Solución 1" o "Solución 2".
En "Solución 1" no se aplica, porque, la herencia, se requiere en cada caso.
"Solución 2", "Mixins" pueden aplicarse, pero, después de diseñar las clases y las jerarquías.
Los mixins son una alternativa para la herencia basada en interfaz o la herencia múltiple basada en clases.
Mi propuesta, Solución 3, se llama a veces Patrón de diseño de "Jerarquía paralela" o Patrón de diseño de "Jerarquía dual".
Muchos desarrolladores / diseñadores no estarán de acuerdo y creen que no debería existir. Pero lo he utilizado yo mismo y otros desarrolladores como una solución común para problemas, como el de su pregunta.
Otra cosa que falta. En sus soluciones anteriores, el problema principal no era usar "mixins" o "interfaces", sino, para refinar, primero, el modelo de sus clases y luego usar una función de lenguaje de programación existente.
fuente
canvas.viewee
y todos sus descendientes necesitan un método llamadopaint()
. Ni elcommon
ni lassvg
clases lo necesitan. Pero en su solución, la jerarquía está dentrocommon
, nocanvas
osvg
como en mi solución 2. Entonces, ¿cómopaint()
termina exactamente en todas las subclases decanvas.viewee
si no hay herencia allí?paint()
debe mover o declarar en "canvas :: viewee". La idea general del patrón se mantiene, pero se puede requerir que algunos miembros se muevan o cambien.canvas::viewee
?En un artículo titulado Patrones de diseño para tratar las jerarquías de herencia dual en C ++ , el tío Bob presenta una solución llamada Escalera al cielo . Es la intención declarada:
Y el diagrama proporcionado:
Aunque en la solución 2 no hay herencia virtual, está muy de acuerdo con el patrón Stairway to Heaven . Por lo tanto, la solución 2 parece razonable para este problema.
fuente