Disaccoppiamento del codice UI?

7

Nella mia applicazione ho diversi gestori di eventi che eseguono alcune azioni in risposta a eventi dell'interfaccia utente come un clic del pulsante o una selezione di menu. Il codice in questi gestori di eventi è simile a questo ad esempio:

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.");
    }

}

Questo è tipico dei miei gestori di eventi. Non codifico i gestori di eventi monolitici, ma "incollano" vari pezzi insieme. Di solito ho alcune classi di business logic / servizi a cui l'interfaccia utente fa riferimento e nei gestori di eventi faccio chiamate a uno o più di questi per eseguire l'attività desiderata.

Quello che mi chiedo è che è ancora troppo accoppiato? Se volessi trasformarlo in un'applicazione da riga di comando dovrei estrarre frammenti dal codice dell'interfaccia utente. Nell'esempio sopra, c'è un'azione composta in corso, quindi dovrei pensare che dovrebbe assomigliare più:

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.");
    }

}

È giusto? Nel codice sopra che cosa dovrebbe essere "applicazione"? Un controller? È la giusta astrazione per questo? Ogni volta che ho bisogno di eseguire qualche compito in un gestore dell'interfaccia utente, dovrebbe sempre essere un one-liner (a parte ottenere dati dentro o fuori dalle caselle di testo, ecc.). Tutti hanno un puntatore a un tutorial che copre davvero questo (in qualsiasi linguaggio di programmazione )?

Inoltre, se in effetti dovresti astrarre azioni composte, come propagare i messaggi di errore dall'interno dell'astrazione composta per tornare all'interfaccia utente? Una gerarchia di eccezioni? Singola eccezione con codice di errore?

Aggiorna

Sto guardando il modello di comando che sembra promettente.

UPDATE 2

Alla fine ho deciso di refactoring il mio codice come descritto nel documento Humble Dialog Box nella risposta accettata. La mia comprensione è che questo è un modello di tipo Model View Presenter (MVP) e più specificamente una vista passiva. Il refactoring sta andando liscio. Ci sono alcuni spigoli nella mia comprensione della piena applicazione del pattern quando si tratta di cose come visualizzare finestre di messaggio e finestre di dialogo aperte o gestire i timer di eventi, ma nel complesso sono più contento del mio codice; l'accoppiamento stretto mi dava fastidio.

Ho trovato anche questo "pacchetto": modelli & pratica Guida allo sviluppatore del client Web: pacchetto Model View Presenter (MVP) che contiene codice MVP di esempio. È un progetto web mentre la mia attuale implementazione è desktop ma credo che la bellezza di MVP sia che in parte non importa. Una cosa che ho ottenuto è stata quella di iniettare i miei oggetti del livello di servizio nel presentatore piuttosto che introdurli nella mia classe di presentazione.

    
posta User 14.10.2011 - 21:27
fonte

2 risposte

1

Se vuoi imparare come disaccoppiare meglio la tua GUI dalla tua logica aziendale, ti suggerisco di leggere l'articolo di Michael Feathers "La umile finestra di dialogo"

Se ti sei familiarizzato con questo, e vuoi ancora qualcosa di più, ecco una serie Blog "Build Your Own CAB" che spiega tutte le varianti importanti di "MVC":

link

EDIT: secondo il tuo codice di esempio: quando userai "MVP", ciò significherebbe non avere un "oggetto dio" application in cui si posiziona MonitorUrlsFromFile , ma un controller (o "presentatore") class MyDialogController in prima persona. Pensaci quando il tuo programma otterrà 20 dialoghi e tutti metteranno la logica del gestore in una classe application - che si concluderà facilmente con una classe con 5000 righe di codice o più, che è decisamente troppo grande.

In base all'approccio MVP, dovrai solo rifattorizzare MonitorUrlsFromFile in un'altra posizione più generica, quando hai bisogno di questa funzione da più di una finestra di dialogo e desideri riutilizzarla. Anche in questo caso, non vorrei che fosse una classe application , funzioni di gruppo migliori per il monitoraggio url o url in una classe di utilità.

    
risposta data 16.10.2011 - 20:20
fonte
5

Il tuo refactoring va bene, specialmente se MonitorUrlsFromFile() è un codice duplicato. Non hai bisogno di una sensibilità "disaccoppiamento dalla UI" per giustificare tale refactoring; può essere giustificato per i suoi meriti.

Lo schema di propagazione degli errori dipende da una serie di fattori. In genere, utilizzo un oggetto BackgroundWorker o un thread o qualcosa di simile per ricevere eventi di stato da un'attività in background di lunga durata, ma il progetto a cui sto lavorando ha diverse convalide di regole aziendali che appariranno sull'interfaccia utente, quindi il mio approccio è andando a cambiare lì.

Il modo più semplice per gestire la propagazione degli errori di base consiste nel fornire un gestore di eccezioni di primo livello nel tuo metodo Main() o in qualunque modo richiami il tuo ciclo di elaborazione.

    
risposta data 14.10.2011 - 21:39
fonte

Leggi altre domande sui tag