Visualizzazione ed elaborazione di oggetti in un elenco?

3

Attualmente ho un codice come questo per visualizzare alcuni oggetti che soddisfano alcuni criteri in una griglia:

// simplified
void MyDialog::OnTimer()
{
    UpdateDisplay();
}

void MyDialog::UpdateDisplay()
{
    std::list<MyClass *> objects;
    someService_->GetObjectsMeetingCriteria(objects, sortField, sortDirection);

    for(std::list<MyClass *>::iterator it = objects.begin();
        it != objects.end() ; it ++)
    {
        AddObjectToGrid(*it);
    }

}   

Funziona bene. Ora il requisito è arrivato per elaborare questi oggetti quando soddisfano alcuni criteri. Le informazioni sugli oggetti possono cambiare rapidamente, quindi sarebbe più opportuno verificare se un oggetto soddisfa i criteri e poi elaborarlo immediatamente e continuare così.

La mia domanda è come miglior architetto per gestire la visualizzazione e l'elaborazione. Potrei semplicemente aggiungere un metodo per elaborare l'oggetto, ad esempio:

for(std::list<MyClass *>::iterator it = objects.begin();
    it != objects.end() ; it ++)
{
    ProcessObject(*it);
    AddObjectToGrid(*it);
}

Comunque idealmente verificherei se un oggetto soddisfa i criteri immediatamente prima di elaborarlo per garantire che agisca sulle informazioni più recenti. In questo esempio, tutti gli oggetti vengono controllati per vedere se corrispondono ai criteri e poi in seguito vengono elaborati.

Inoltre sono preoccupato che questo accoppi il codice di elaborazione con il codice di visualizzazione anche se non sono sicuro di come separarli o se è addirittura necessario. Potrei risolvere il problema in questo modo:

void MyDialog::OnTimer()
{
    ProcessObjects();
    UpdateDisplay();
}

Ma poi sto ripetendo l'elenco degli oggetti due volte, una volta da elaborare e una volta da visualizzare.

Finalmente ho potuto fare qualcosa di simile:

// simplified
void MyDialog::OnTimer()
{
    ProcessAndDisplayObjects();
}

void MyDialog::ProcessAndDisplayObjects()
{
    std::list<MyClass *> objects;
    someService_->GetAll(objects, sortField, sortDirection);

    for(std::list<MyClass *>::iterator it = objects.begin();
        it != objects.end() ; it ++)
    {
        if(someService->MeetsCriteria(*it))
        {
          ProcessObject(*it);
          AddObjectToGrid(*it);
        }
    }
}   

Nel complesso ciò che mi trattiene è che sono preoccupato di collegare insieme il codice di visualizzazione e elaborazione e che il codice funzioni in modo efficiente perché l'elaborazione tempestiva è fondamentale. Come posso strutturare meglio questo codice?

    
posta User 12.10.2011 - 02:03
fonte

2 risposte

1

Descriverò prima l'ideale, poi alcuni abbastanza buoni entro i limiti che potresti avere. Idealmente si desidera che il codice di elaborazione si trovi al di fuori della classe di dialogo, si desidera che l'elaborazione avvenga separatamente su un thread diverso dal thread GUI, affinché il codice di elaborazione venga avvisato quando i dati cambiano in modo da rendere necessaria l'elaborazione e quindi per notificare la finestra di dialogo per aggiornare i dati anziché utilizzare un timer. (I timer hanno il problema di essere troppo granulosi o non sufficientemente granulari, troppo e mangiano cicli inutilmente, non abbastanza e la GUI non risponde.)

OK, probabilmente non sei preparato a fare thread e notifiche separati, ma è comunque importante spostare il codice di elaborazione fuori dalla finestra di dialogo. La vostra preoccupazione sull'accoppiamento dei due processi è ben fondata, anche se per ora risulta essere un problema banale, i requisiti e il cambio di codice. Questo è il tuo miglior approccio per disaccoppiarli, e rende molto più semplice l'aggiunta di qualsiasi delle altre caratteristiche sopra descritte. La cosa importante, anche se il timer genera sia l'elaborazione che l'aggiornamento, è che l'aggiornamento faccia il meno possibile, cioè chiama alcune funzioni per ottenere un valore da inserire nella griglia e nient'altro. L'elaborazione dovrebbe essere eseguita entro il momento in cui inizia. Supponendo che non ci siano altre modifiche strutturali al tuo codice, sceglierei qualcosa come:

OnTimer()
{
  if (!processor.IsReady())
    processor.process();
  UpdateDisplay()
}

Successivamente, se vuoi generare un nuovo thread per la chiamata a process (), è facile farlo chiamando processthread.start() o qualsiasi cosa invece di processor.process(); e ritornando, lasciando che il successivo ciclo di timer raccolga i dati. Se vuoi avere un modo diverso di avviare la routine del processo (un notificatore che i dati sono cambiati, o un secondo timer, o qualcosa del genere), puoi modificare quanto sopra in:

OnTimer()
{
  if (processor.IsReady())
    UpdateDisplay();
}

Non preoccuparti di ripetere l'iterazione due volte, a meno che il set di dati non sia davvero enorme. E se è abbastanza grande da preoccuparsi, è un motivo in più per cercare di farlo fuori dal thread della GUI, perché non importa quanto lo si consideri, causerà il singhiozzo del display. Se l'oggetto del processore è dietro un'interfaccia, tanto meglio, quindi puoi refactoring in seguito senza influenzare il codice di dialogo. E puoi aggiungere codice in OnTimer () per controllare un flag per vedere se non solo i dati sono stati elaborati, ma è cambiato dal tuo ultimo aggiornamento, e saltarlo se non lo è.

    
risposta data 12.10.2011 - 03:28
fonte
1

Dato che sei (almeno apparentemente) usando C ++, proverei a scrivere il codice per adattarlo un po 'più a quello che fanno gli iteratori e gli algoritmi standard. Vorrei iniziare con una piccola facciata per dare alla tua "griglia" un'interfaccia iteratore:

// warning: untested code.
struct grid_proxy { 
    grid_proxy &operator=(MyClass const &item) { 
        AddObjectToGrid(item);
        return *this;
    }
};

class grid_iterator : public std::iterator<std::output_iterator_tag, MyClass> { 
public:
    operator*() { return grid_proxy(); }
    grid_iterator operator++() { return *this; }
    grid_iterator operator++(int) { return *this; }
};

Quindi scriverei un piccolo algoritmo che probabilmente dovrebbe essere nella libreria, ma non lo è: a transform_if :

template <class InputIterator, class OutputIterator, class UnaryOperation, class Predicate>
void transform_if(InputIterator start, InputIterator stop, 
                  OutputIterator out, 
                  UnaryOperation op, 
                  Predicate pred) 
{
    while (start != stop) {
        if (pred(*start))
            *out++ = op(*start);
        ++start;
    }
}

Con questi, l'operazione complessiva diventa qualcosa del tipo:

std::list<MyClass *> objects;
someService_->GetAll(objects, sortField, sortDirection);

transform_if(objects.begin(), objects.end(), grid_iterator(), MeetsCriteria());

Al momento, presumo che MeetsCriteria verrà scritto come un functor anziché come funzione membro. Se è possibile utilizzare le funzionalità di C ++ 11, si potrebbe prendere in considerazione l'utilizzo di un lambda invece.

In ogni caso, questo fornisce un de-accoppiamento piuttosto equo della maggior parte del codice. transform_if semplicemente sa che sta scrivendo su un iteratore - solo il grid_iterator ha la minima idea che sia coinvolto AddObjectToGrid. Un diverso iteratore può produrre l'output in modo completamente diverso.

Allo stesso modo, transform_if non si preoccupa affatto dei criteri o di come è determinato, a patto che possa essere invocato come una funzione con la firma giusta.

Anche se questo impone un po 'di overhead nella scrittura di un tipo di iteratore conforme e così via, ha alcuni vantaggi. Il suddetto disaccoppiamento è certamente utile. Il fatto che chiunque conosca la libreria C ++ ragionevolmente bene saprà cosa succede senza quasi pensarci è anche una cosa molto buona di norma.

    
risposta data 12.10.2011 - 04:42
fonte

Leggi altre domande sui tag