¿Desacoplamiento del código UI?

7

En mi aplicación tengo varios controladores de eventos que realizan alguna acción en respuesta a los eventos de la interfaz de usuario, como hacer clic en un botón o seleccionar un menú. El código en estos controladores de eventos se ve así, por ejemplo:

void MyDialog::OnCommandLoadFile()
{
    char strFilter[] = { "Text Files (*.txt)|*.txt|All Files (*.*)|*.*||" };

    CFileDialog fileDlg(TRUE, ".txt", NULL, 0, strFilter);

    if( fileDlg.DoModal() == IDOK )
    {
        const std::string path = fileDlg.GetPathName();

        std::vector<std::string> urls;
        int maxNumberOfUrls = settings_->Get<int>(Settings::MaxNumberOfUrls);
        UrlProvider *urlProvider = new FileUrlProvider(path);
        urlProvider->GetUrls(urls, maxNumberOfUrls);

        webMonitoringService_->MonitorUrls(urls);

        DisplayMessage("URLs loaded.");
    }

}

Esto es típico de mis controladores de eventos. No codifico controladores de eventos monolíticos, pero sí "pegan" varias piezas. Por lo general, tengo algunas clases de lógica de negocios / servicio a las que la UI hace referencia y, en el caso de los controladores, hago llamadas a uno o más de estos para realizar la tarea deseada.

Lo que me pregunto es si esto todavía está demasiado acoplado. Si quisiera convertir esto en una aplicación de línea de comandos, tendría que extraer fragmentos del código de la interfaz de usuario. En el ejemplo anterior, hay una acción compuesta en marcha, por lo que si creo que debería parecerse más a:

void MyDialog::OnCommandLoadFile()
{
    char strFilter[] = { "Text Files (*.txt)|*.txt|All Files (*.*)|*.*||" };

    CFileDialog fileDlg(TRUE, ".txt", NULL, 0, strFilter);

    if( fileDlg.DoModal() == IDOK )
    {
        const std::string path = fileDlg.GetPathName();

        application->MonitorUrlsFromFile(path);

        DisplayMessage("URLs loaded.");
    }

}

¿Es eso correcto? En el código anterior, ¿qué sería "aplicación"? Un controlador? ¿Es esa la abstracción correcta para esto? Cada vez que necesito realizar alguna tarea en un controlador de IU, debería ser de una sola línea (aparte de obtener datos dentro o fuera de los cuadros de texto, etc.) Cualquiera tiene un puntero a un tutorial que realmente cubra esto (en cualquier lenguaje de programación )?

Además, si de hecho debería abstraer acciones compuestas, ¿cómo propagar los mensajes de error dentro de la abstracción compuesta de nuevo a la interfaz de usuario? ¿Una jerarquía de excepciones? ¿Excepción única con código de error?

ACTUALIZAR

Estoy viendo el patrón de comando que parece prometedor.

ACTUALIZACIÓN 2

Al final, decidí refactorizar mi código como se describe en el documento del cuadro de diálogo de Humble en la respuesta aceptada. Mi entendimiento es que este es un patrón de tipo de presentador de vista modelo (MVP) y más específicamente una vista pasiva. La refactorización va bien. Mi comprensión de la aplicación completa del patrón se refiere a aspectos como la visualización de cuadros de mensajes y cuadros de diálogo de apertura de archivos o el manejo de temporizadores de eventos, pero en general estoy más contento con mi código; el acoplamiento apretado me estaba molestando.

También encontré este "paquete": patterns & Prácticas Guía para desarrolladores de clientes web: Paquete de presentador de vista de modelo (MVP) que tiene un código MVP de muestra. Es un proyecto web, mientras que mi implementación actual es de escritorio, pero creo que esa es la belleza de MVP, en parte no importa. Una cosa que obtuve de eso fue que la dependencia inyecta los objetos de la capa de servicio en el presentador en lugar de "agregarlos" dentro de mi clase de presentador.

    
pregunta User 14.10.2011 - 21:27

2 respuestas

1

Si desea aprender cómo desacoplar mejor su GUI de su lógica empresarial, le sugiero que lea el artículo de Michael Feathers "El humilde cuadro de diálogo"

Si te familiarizaste con eso y quieres algo más de eso, aquí tienes una serie de Blog "Build Your Own CAB" que explica todas las variantes importantes de "MVC":

enlace

EDIT: según su código de ejemplo: cuando vaya a usar "MVP", eso significará no tener un "objeto divino" application donde coloca MonitorUrlsFromFile , sino un controlador (o "presentador") class MyDialogController en primera mano. Piense en ello cuando su programa obtenga 20 diálogos y todos ellos ubicarían su lógica de manejador en una clase application , que fácilmente terminará con una clase con 5000 líneas de código o más, lo que definitivamente es demasiado grande.

Por enfoque de MVP, solo refactorizará MonitorUrlsFromFile a otro lugar, más general, cuando necesite esta función de más de un diálogo y desee reutilizarla. Incluso entonces no haría de esto una clase application , mejores funciones de grupo para URL o monitoreo de URL en una clase de utilidad.

    
respondido por el Doc Brown 16.10.2011 - 20:20
5

Tu refactorización está bien, especialmente si MonitorUrlsFromFile() es un código duplicado. No necesita una sensibilidad de "desacoplamiento de la interfaz de usuario" para justificar esa refactorización; puede justificarse por sus propios méritos.

El esquema de propagación de errores depende de varios factores. En general, uso un objeto o hilo BackgroundWorker o algo similar para recibir eventos de estado de una tarea en segundo plano de larga ejecución, pero el proyecto en el que estoy trabajando actualmente tiene varias validaciones de reglas empresariales que aparecerán en la interfaz de usuario, por lo que mi enfoque es Va a cambiar allí.

La forma más sencilla de manejar la propagación de errores básica es proporcionar un controlador de excepciones de nivel superior en su método Main() o lo que sea que llame a su ciclo de procesamiento.

    
respondido por el Robert Harvey 14.10.2011 - 21:39

Lea otras preguntas en las etiquetas