Questo problema sembra piuttosto semplice, ma non ho mai conosciuto una soluzione eccezionale. Sto cercando un modo per i componenti di un'applicazione di notificarsi a vicenda mentre sono il più disaccoppiati possibile (sia in fase di compilazione / compilazione sia in fase di esecuzione), ma anche evitando notifiche circolari in modo che i componenti non ho bisogno di auto-mitigare contro. Mi è capitato di riscontrare questo problema (per la centesima volta) in JavaScript al momento, ma questo è incidentale.
Alcuni metodi di disaccoppiamento includono:
-
Iniezione di dipendenza (DI) . In questo caso, utilizzo
require.js
che consente, ad esempio, di sostituire le implementazioni fittizie nei test unitari creando creazioni alternativerequire.config()
. - Invio di eventi . Per esempio. %codice%
- Pubblica / iscriviti (aka pub / sub).
Gli ultimi due sono fondamentalmente varianti del pattern Observer con diversi pro e contro.
Il problema che voglio risolvere non è affrontato specificamente da questi pattern, e non sono sicuro se si tratta di un dettaglio di implementazione o se c'è un pattern da applicare:
-
fooInstance.listen('action string', barInstance.actionHandler)
invia un messaggio (genera un evento, qualunque sia) che la proprietàfooObj
è stata modificata -
"baz"
gestisce questo evento chiamando il proprio metodobarObj.bazChanged()
-
setBaz()
genera un evento che "baz" è cambiato -
barObj.setBaz()
gestisce questo evento ...
Come caso reale, immagina fooObj.bazChanged()
è un componente della GUI, con un cursore per fooObj
, e "tempo"
è un componente di sequencing musicale che riproduce un punteggio. Il cursore dovrebbe influenzare il tempo del sequencer, ma il punteggio può contenere variazioni di tempo, quindi quando si suona il sequencer si dovrebbe influenzare la posizione del cursore. Una soluzione dovrebbe essere non modale.
Un approccio è aggiungere guardie, ad esempio:
function handleTempoChanged(tempo) {
if (this.tempo == tempo) return;
...
}
Funziona, ma sembra una soluzione scadente perché significa che ogni gestore di eventi ha bisogno di assumere che sia necessaria una protezione, che è brutta norma generale e spesso non richiesta, OPPURE essere a conoscenza degli altri componenti del sistema che renderebbero un ciclo possibile. Probabilmente, questo punto è sbagliato, le guardie dovrebbero sempre essere usate se il gestore sta andando a lanciare un evento modificato direttamente o indirettamente, ma questo sembra ancora una logica di caldaia. Questa potrebbe essere l'unica risposta alla mia domanda ...
Esiste un modello generale per trattare i potenziali cicli come descritto? Si noti che questo è non su sincrono contro asincrono; in entrambi i casi i cicli possono verificarsi.
MODIFICATO per riflettere le opinioni dei commentatori:
Mi rendo conto che è possibile eliminare il boilerplate, usando una sorta di mixin. In pseudo-codice:
class ObservablePropsMixin:
// generic setter with gaurd
function set(propName, value):
if this[propName] = value: return
this[propName] = value
_fire(propName, value)
// generic method to add listeners
function observe(propName, handler):
_observers[propName].add(handler)
// private event dispatching method
function _fire(propName, value):
foreach observer in _observers[propName]:
observer.call(propName, value)
...
Questo è semplificato per concentrarsi sulla mia domanda, ma un vero mixin implementerebbe altre semantica di pubsub o eventi o segnali, analoga a qualsiasi implementazione di dispatcher di eventi. I componenti che devono essere osservatori o osservabili dovrebbero ereditare / estendersi dal mixin (quanto sopra presuppone che una certa forma di ereditarietà multipla o aggregata sia possibile nella lingua, che potrebbe essere modificata per funzionare invece con la composizione, ad esempio ObservableMixin.constructor (osservatoObject) piuttosto che usando "questo".