Tengo un software CAD/CAM para la máquina de corte de metales. Así que tengo algo de experiencia con estos problemas.
Cuando convertimos por primera vez nuestro software (¡se lanzó por primera vez en 1985!) A un objeto orientado al diseño, hice exactamente lo que no te gusta. Los Objetos e Interfaces tenían Draw, WriteToFile, etc. Descubrir y leer acerca de los Patrones de Diseño a mitad de la conversión ayudaron mucho, pero todavía había muchos malos olores de código.
Eventualmente me di cuenta de que ninguno de estos tipos de operaciones era realmente la preocupación del objeto. Pero más bien los diversos subsistemas que necesitaban para hacer las diversas operaciones. Manejé esto usando lo que ahora se llama un objeto de comando Passive View y una interfaz bien definida entre las capas de software.
Nuestro software está estructurado básicamente así
- Las formas de aplicación varia forma interfaz. Estas formas son una cosa que pasa eventos de shell a la capa de interfaz de usuario.
- Capa de interfaz de usuario que recibe eventos y manipula formularios a través de la interfaz de formulario.
- La capa de interfaz de usuario ejecutará comandos que implementan todos la interfaz de comandos
- El objeto de la interfaz de usuario tiene interfaces propias con las que el comando puede interactuar.
- Los comandos obtienen la información que necesitan, la procesan, manipulan el modelo y luego informan a los objetos de la interfaz de usuario que luego hacen todo lo necesario con los formularios.
- Finalmente, los modelos que contienen los diversos objetos de nuestro sistema. Como programas de formas, rutas de corte, tablas de corte y hojas de metal.
Así que el dibujo se maneja en la capa de interfaz de usuario. Tenemos diferentes programas para diferentes máquinas. Entonces, si bien todos nuestros programas comparten el mismo modelo y reutilizan muchos de los mismos comandos. Manejan cosas como dibujar muy diferente. Por ejemplo, una mesa de corte es diferente para una máquina enrutadora que para una máquina que usa una antorcha de plasma, a pesar de que ambas son esencialmente una mesa plana gigante X-Y. Esto porque al igual que los automóviles, las dos máquinas están construidas de manera diferente, de modo que existe una diferencia visual para el cliente.
En cuanto a las formas lo que hacemos es la siguiente
Tenemos programas de forma que producen trayectorias de corte a través de los parámetros introducidos. La ruta de corte sabe qué programa de forma produjo. Sin embargo, un camino de corte no es una forma. Es solo la información necesaria para dibujar en la pantalla y cortar la forma. Una razón para este diseño es que las rutas de corte se pueden crear sin un programa de formas cuando se importan desde una aplicación externa.
Este diseño nos permite separar el diseño de la trayectoria de corte desde el diseño de la forma, que no siempre son la misma cosa. En su caso, lo único que necesita para empacar es la información necesaria para dibujar la forma.
Cada programa de formas tiene varias vistas que implementan una interfaz IShapeView. A través de la interfaz IShapeView, el programa de forma puede indicarle a la forma de forma genérica que tenemos cómo configurarse para mostrar los parámetros de esa forma. La forma de forma genérica implementa una interfaz IShapeForm y se registra con el Objeto ShapeScreen. El Objeto ShapeScreen se registra con nuestro objeto de aplicación. Las vistas de formas usan cualquier pantalla de formas que se registre a sí misma con la aplicación.
El motivo de las vistas múltiples que tenemos los clientes a los que les gusta ingresar formas de diferentes maneras. Nuestra base de clientes se divide a la mitad entre aquellos a los que les gusta ingresar parámetros de forma en forma de tabla y aquellos a quienes les gusta ingresar con una representación gráfica de la forma que tienen delante. También necesitamos acceder a los parámetros a veces a través de un diálogo mínimo en lugar de nuestra pantalla de entrada de forma completa. De ahí las múltiples vistas.
Los comandos que manipulan las formas se clasifican en una de dos categorías. O manipulan la ruta de corte o manipulan los parámetros de forma. Para manipular los parámetros de forma en general, los devolvemos a la pantalla de entrada de forma o mostramos el diálogo mínimo. Vuelva a calcular la forma y muéstrela en la misma ubicación.
Para la ruta de corte agrupamos cada operación en un objeto de comando separado. Por ejemplo, tenemos objetos de comando
ResizePath RotatePath MovePath SplitPath y así sucesivamente.
Cuando necesitamos agregar nuevas funciones, agregamos otro objeto de comando, buscamos un menú, un teclado corto o un botón en la pantalla de UI correcta y configuramos el objeto UI para ejecutar ese comando.
Por ejemplo
CuttingTableScreen.KeyRoute.Add vbShift+vbKeyF1, New MirrorPath
o
CuttingTableScreen.Toolbar("Edit Path").AddButton Application.Icons("MirrorPath"),"Mirror Path", New MirrorPath
En ambos casos el objeto MIRRORPATH Comando se está asociado con un elemento de interfaz de usuario deseado. En el método de ejecución de MirrorPath está todo el código necesario para duplicar la ruta en un eje particular. Es probable que el comando tenga su propio cuadro de diálogo o utilice uno de los elementos de la interfaz de usuario para preguntar al usuario qué eje duplicar. Nada de esto está haciendo un visitante, o agregando un método a la ruta.
Encontrará que se puede manejar mucho combinando acciones en comandos. Sin embargo, advierto que no es una situación en blanco y negro. Todavía encontrará que ciertas cosas funcionan mejor como métodos en el objeto original. En mi experiencia, descubrí que tal vez el 80% de lo que solía hacer en los métodos se podía mover al comando. El último 20% simplemente funciona mejor en el objeto.
Ahora es posible que a algunos no les guste esto porque parece violar las encapsulaciones. Desde el mantenimiento de nuestro software como un sistema orientado a objetos durante la última década, tengo que decir que lo más importante a largo plazo que puede hacer es documentar claramente las interacciones entre las diferentes capas de su software y entre los diferentes objetos.
El agrupamiento de acciones en Objetos de comando ayuda con esta meta mucho mejor que una dedicación servil a los ideales de la encapsulación.Todo lo que se necesita hacer para Duplicar una ruta está incluido en el Objeto de comando Mirror Path.
Solo una nota aparte, ya que es poco probable que pueda cambiar su idioma: hay idiomas que soportan directamente varias funciones genéricas de despacho. – Svante
Gran pregunta. Solo quería proporcionar un contrapunto. A veces su problema con (5) puede ser algo bueno. Uso el patrón de visitante cuando tengo alguna funcionalidad que debe actualizarse cuando se define un nuevo subtipo IShape. Tengo una interfaz IShapeVisitor que define qué métodos son necesarios. Siempre que esa interfaz se actualice con el nuevo subtipo, mi código no se compila hasta que se actualice la funcionalidad crítica. Para algunas situaciones, esto puede ser muy útil. – oillio
Estoy de acuerdo con @oillio, pero también podría aplicarlo como método abstracto en IShape. Lo que el patrón Visitor le compra en un idioma OO puro es la localidad de la función (frente a la localidad de clase) y, por lo tanto, una separación de preocupaciones.En cualquier caso, use el patrón de visitante que debe romperse explícitamente en tiempo de compilación cuando desee forzar la adición de nuevos tipos para que se revisen con cuidado. –