modello di ricalcolo per GUI

3

Spesso mi imbatto in questo problema.
Supponi di avere un modulo con alcuni controlli:

A
B
C = A + B
D 
E = C * 2 ONLY IF D==TRUE
F
G = A - F

L'utente riempie A, poi B.
Il sistema calcola C.
L'utente riempie D (booleano)
Il sistema calcola E
L'utente riempie F
Il sistema calcola G

Ma ora:
L'utente cancella un annuncio Il sistema cancella C, E e G
L'utente cancella il B
Il sistema non fa niente
L'utente inserisce un annuncio Il sistema ricalcola solo F
E così di seguito.

C'è chiaramente un albero di dipendenze, e non è difficile da capire. Ma pragmaticamente ho trovato abbastanza difficile implementarlo senza "framework" per modellarlo.
Che siano input in un modulo HTML o controlli in un'applicazione javafx, il problema di creare un algoritmo completo di "ricalcolo" rimane ancora.
(Javafx offre un'architettura "vincolante" che, nonostante sia un po 'prolissa, è tuttavia molto potente e indirizza (parte di) questo specifico problema). Ma sono più interessato a una metodologia che può essere utilizzata attraverso le diverse piattaforme. Io uso un sacco di tecniche per risolvere i problemi che si presentano ma non ancora una metodologia, che è un modo per passare dalla definizione all'implementazione con la quantità minima di prove.
Le tecniche sono le buone vecchie: invalidazioni, init pigro, che avvolge ogni controllo in un 'nodo' di un grafico di ricalcatura.
Ma sento che ci dovrebbe essere un approccio più canonico a questo problema ricorrente, e un documento in cui questo approccio è spiegato e così via. Qualcuno mi può suggerire dove posso trovare tali informazioni?

MODIFICA 1: termini del problema

  • Uno degli obiettivi chiave di tale soluzione è avere un modo per organizzare il codice in modo prevedibile, in modo che, conoscendo il metodo, dovrebbe essere facile valutarne la correttezza.
  • Stato iniziale: molti sistemi non riescono a tornare al loro stato iniziale, questo è l'odore di un design scadente che tale metodologia dovrebbe impedire
  • Dovrebbe essere progettato per casi reali come (riprendendo l'esempio): quando l'utente cambia A, il sistema dovrebbe cancellare B nel caso in cui erano accoppiati. È facile pensare a questi casi: un utente seleziona un contry e il sistema fornisce un elenco di regioni che l'utente può scegliere. Se l'utente cambia città, la regione precedentemente scelta deve essere cancellata anche se non è strettamente un campo dipendente poiché non viene calcolata dal sistema.

Come esempio di una soluzione molto parziale:

  1. Ogni controllo dovrebbe avere un metodo di 'aggiornamento' di supporto che dovrebbe attivare solo l'aggiornamento dei controlli 'collegati direttamente'. Questa ipotesi dovrebbe garantire che quando C è basato su A, A è sempre aggiornato.

    function update_A () {   update_C; }

    function update_B () {   update_C; }

    function update_C () {   C.setValue (A + B);   update_E (); }

    ecc. ecc.

  2. poiché non ci devono essere riferimenti circolari, questa funzione di aggiornamento dovrebbe formare un 'albero'.

Altri contributi a venire.

    
posta AgostinoX 29.09.2014 - 21:29
fonte

3 risposte

1

Supponendo che la quantità di ricalcolo sia banale, allora una sorta di struttura Model-View-Controller sarebbe appropriata:

  1. L'utente cambia B
  2. La vista notifica al controllore (tramite evento, ecc.) che B è cambiato (o che qualcosa è cambiato) e il controllore passa B (o tutto ciò che è modificabile) al modello.
  3. Il modello aggiorna i valori interni e espone i valori per G, ecc.
  4. Il controller copia i valori dei risultati dal modello alla vista.

In questo modo, la dipendenza esiste solo nel modello. La View è molto stupida, e il Controller sa quali campi sono letti / scritti e che sono di sola lettura, ma non sa nulla delle interdipendenze.

Modifica :

Voglio chiarire il termine "Modello". In genere ci sono più di un livello di Model in un programma, anche se parliamo sempre di "The Model". Nel caso di un'applicazione client che parla con un server remoto, solitamente c'è un modello di server e un modello client. Se il client è un browser Web, il modello client potrebbe essere scritto in JavaScript e il modello server potrebbe essere in qualsiasi linguaggio lato server come C #, PHP, ecc.

C'è una domanda naturale che si pone: "Questa logica di ricalcolo appartiene al modello del cliente, al modello del server o ad entrambi?" Dal momento che non puoi fidarti del client, sei davvero obbligato a implementare sempre il ricalcolo nel Modello Server, e facoltativamente implementarlo nel Modello Client se vuoi evitare inutili round-trip verso il server. Sfortunatamente questo può finire per violare il principio dell'unica volta sola, a meno che tu non sia abbastanza fortunato da lavorare in un linguaggio / framework che ti permetta di riutilizzare determinati moduli e logica sia lato server che lato client (Nodo js?)

    
risposta data 29.09.2014 - 21:52
fonte
1

The tecniques are the good old: invalidations, lazy init, wrapping each control in a 'node' of a recalc-graph. But i feel that there should be a more canonical approach to this recurrent problem, and a paper where this approach is explained and so on. Can anyone suggest me where can i find such information?

Credo che l'approccio canonico sarebbe il modello di stato.

Dai un'occhiata al link , ad esempio.

Ovviamente non è associato alla programmazione del gioco in particolare, mi piace solo questa particolare spiegazione.

Specialmente adatto è la macchina di stato gerarchica, dove c'è più interdipendenza tra gli stati.

Un'altra risorsa, incentrata sulla macchina di stato gerarchica nello specifico: link

Ogni stato sa cosa succede quando è acceso, cosa dovrebbe succedere quando è spento. Può attivare altri stati, causando probabilmente una cascata di aggiornamenti. Ciò può consentire APis flessibili e manutenibili.

Dai un'occhiata a questo progetto, ad esempio - scritto nel mio linguaggio di programmazione preferito (C #): link (I'm in nessun modo associato al progetto o ai suoi autori).

Non intendo il codice, ma i campioni. Sono chiari e istruttivi e quindi possono già essere una buona indicazione su come implementare la cosa:

var phoneCall = new StateMachine<State, Trigger>(State.OffHook);

phoneCall.Configure(State.OffHook)
    .Permit(Trigger.CallDialed, State.Ringing);

phoneCall.Configure(State.Ringing)
    .Permit(Trigger.HungUp, State.OffHook)
    .Permit(Trigger.CallConnected, State.Connected);

phoneCall.Configure(State.Connected)
    .OnEntry(() => StartCallTimer())
    .OnExit(() => StopCallTimer())
    .Permit(Trigger.LeftMessage, State.OffHook)
    .Permit(Trigger.HungUp, State.OffHook)
    .Permit(Trigger.PlacedOnHold, State.OnHold);

// ...

phoneCall.Fire(Trigger.CallDialled);
Assert.AreEqual(State.Ringing, phoneCall.State);

// ...

phoneCall.Configure(State.OnHold)
    .SubstateOf(State.Connected)
    .Permit(Trigger.TakenOffHold, State.Connected)
    .Permit(Trigger.HungUp, State.OffHook)
    .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);

Neat, huh?

    
risposta data 29.09.2014 - 22:21
fonte
1

Bene, perché non iniziare con un approccio molto semplice? Hai campi "livello 0" in cui l'utente può immettere valori, campi "livello 1" che dipendono solo dai campi "livello 0", "livello 2" a seconda dei campi "livello 1" o "livello 0" e così via . Dietro ogni campo "livello n" con n > 0 c'è una funzione che si occupa di valori di input incompleti (come C = f (A, B) con f (A, B) = A + B se A e B sono numerici, e "nessun valore" se non.

Ora, ogni volta che un campo "livello 0" viene modificato da un utente, ricalcola tutto , e fallo nell'ordine dei livelli crescenti 1, 2 3, ... e così via.

Probabilmente non è la soluzione più efficiente, ma in molti casi reali è abbastanza efficiente. È l'approccio da cui dovresti iniziare, e solo se si rivela che non è abbastanza veloce, dovresti cercare di implementare qualcosa di più sofisticato. Altrimenti rischi di incappare nella trappola "ottimizzazione prematura".

    
risposta data 29.09.2014 - 22:58
fonte

Leggi altre domande sui tag