Due ingressi utente che si alterano a vicenda

3

Ho un'interfaccia utente con due caselle di input numerici, importo invio e importo ricezione . I valori sono in valute diverse e sono legati da un tasso di cambio. Ovvero, l'importo di ricezione dovrebbe sempre essere importo di invio * tasso di cambio . Ma l'utente è autorizzato a fare clic nella casella di input e a modificare il valore, il che fa sì che l'altro valore venga aggiornato.

Ho un semplice sistema per osservare le modifiche ad entrambi i valori e aggiornare l'altro. Ho avuto problemi con loop infiniti dei valori che si aggiornano a vicenda perché sono float e moltiplicando quindi dividendo per il tasso di cambio non sempre si crea esattamente lo stesso valore. Così ho aggiunto un semplice flag per ignorare gli aggiornamenti mentre il programma di aggiornamento stesso è in esecuzione.

Ma ora aggiungo più funzionalità quando l'utente aggiorna un campo quantità, richiedendo ad altri oggetti di ascoltare i campi. Di conseguenza alcuni di questi oggetti finiscono per cambiare uno dei campi di quantità, che innesca un ciclo infinito.

Ho sempre pensato che fosse una fragile modellazione del problema, ma non ho pensato a qualcosa di meglio. Esiste uno schema o un approccio stabilito per modellare due valori che si aggiornano a vicenda?

    
posta Angus 13.01.2015 - 22:18
fonte

4 risposte

2

Il modo giusto di implementare le notifiche di stato e di cambiamento è sintetizzato dal seguente metodo setter: pseudocodice:

method setValue( T newValue )
{
    if( newValue equals existingValue )
        return;
    existingValue = newValue;
    issue state change notification;
}

È noto che funziona e funziona perfettamente. Nessun ciclo infinito, nessuna oscillazione, nessuna notifica non necessaria, nessun problema.

Fino a quando un giorno qualcuno decide di usare i float.

Il tuo problema non è con il concetto ben definito di notifiche di cambiamento di stato e il modello di osservatore, il tuo problema è con il fatto che hai utilizzato un tipo di dati scadente per il lavoro.

Hai cercato di superare i malfunzionamenti causati dall'imprecisione inerente ai float con un approccio che, sebbene non del tutto sbagliato, non era realmente l'approccio corretto per il problema in questione: hai aggiunto un flag per ignorare gli aggiornamenti mentre il programma di aggiornamento stesso è esecuzione.

Quindi, qui ci sono alcuni approcci per risolvere il tuo problema direttamente, invece di lasciarlo stare e cercare di aggirare il problema:

  1. Usa confronti di uguaglianza per i tuoi float: if( abs( float1 - float2 ) < epsilon ) dove epsilon è un numero molto piccolo come 0.00000001, a seconda della precisione desiderata. Per ulteriori informazioni, vedere questo: Cosa c'è di sbagliato nell'uso di == in confrontare i float in Java?

  2. Utilizza un tipo di dati "decimale" o "BigDecimal" invece di float. Se hai a che fare con valori di valuta, dovresti farlo sicuramente . Per ulteriori informazioni, vedere questo: Perché non utilizzare Double o Float per rappresenta la valuta?

risposta data 13.02.2015 - 20:16
fonte
3

C'è un bel video del WWDC di Apple su questo argomento. La soluzione è semplice:

Memorizzato da qualche parte è la verità. La verità non è un numero, è una coppia (numero, valuta). Se un utente immette un numero in un campo Yen, la verità viene modificata in X Yen. Quando viene notificato un campo in dollari, non cambia la verità in dollari. Visualizza l'importo nel miglior modo possibile, ma non cambia la verità facendo ciò. Solo quando l'utente immette un numero, la verità viene modificata.

Quando è stata posta la domanda, c'era il presupposto che ci siano due verità, relative a una formula, ed è qui che inizia il problema. Una verità, non due.

    
risposta data 13.02.2015 - 20:33
fonte
0

Aggiungi un argomento chiamato mittente che può essere incatenato insieme quando gli eventi vengono generati. Come primo passo controlla se l'oggetto corrente si trova sulla catena, in tal caso, contrassegna solo come gestito in modo che non si verifichino aggiornamenti.

Esempio

Button1 avvia il cambiamento.

Sender: Button1

Intercetta Button2, per prima cosa controlla se è già presente nella catena di invio. In questo caso non lo è, quindi esegue un aggiornamento, aggiungendo un altro mittente sulla catena (Button1, Button2) e solleva un evento che è stato modificato. Button1 intercetta, ispeziona la catena, Button1 e Button2 sono sulla catena, quindi non dovrebbe fare un aggiornamento e quindi non verranno più sollevati.

In questo modo l'interfaccia utente non finisce per rincorrere la coda.

    
risposta data 13.01.2015 - 22:47
fonte
0

Le due caselle di input sono due viste sullo stesso valore. Se una casella viene modificata, anche l'altra viene modificata. Pertanto, non ci dovrebbe essere nessun evento separato "on change" a cui iscriversi. I due gestori di eventi immediati emettono solo un evento comune che prende nota di quale casella è stata modificata. Pseudocodice:

class ConvertFromTo(from: Input, to: Input) {
  val on-change = new Observable[ChangeEvent]()
  from.on-change.add(e => on-change.emit(new ChangeEvent(this, Direction.From, e.value))
  to.on-change.add(e => on-change.emit(new ChangeEvent(this, Direction.To, e.value))
}

Ciò consente agli ascoltatori esterni di iscriversi al ConvertFromTo.on-change osservabile combinato. È possibile utilizzare questo livello di astrazione per escludere eventi che non hanno effettivamente modificato il valore di conversione. Se alcuni componenti devono essere notificati in caso di modifiche al valore visualizzato, possono ancora iscriversi direttamente all'evento change di quel campo di input.

    
risposta data 14.01.2015 - 06:28
fonte

Leggi altre domande sui tag