Procedure consigliate per il modello di progettazione osservatore

In .NET il modello di progettazione osservatore viene implementato come set di interfacce. L'interfaccia System.IObservable<T> rappresenta il provider di dati, che è anche responsabile della fornitura di un'implementazione IDisposable che consente agli osservatori di annullare la sottoscrizione alle notifiche. L'interfaccia System.IObserver<T> rappresenta l'osservatore.

Questo articolo descrive le procedure consigliate da seguire quando si implementa il modello di progettazione observer con queste interfacce.

Prendere in considerazione le alternative prima dell'implementazione

Le interfacce IObservable<T> e IObserver<T> sono adatte per scenari di notifica basati su push, ma altri modelli di .NET potrebbero essere più adatti. Per una notifica semplice all'interno di una singola applicazione, usare gli eventi. Per le sequenze asincrone di tipo pull in cui il consumatore controlla il ritmo, utilizzare IAsyncEnumerable<T>. Per i modelli producer-consumer con backpressure, usare System.Threading.Channels. Per la composizione di eventi complessi, il filtro e la trasformazione, usare il pacchetto System.Reactive (Rx.NET) anziché implementare direttamente IObservable<T>. Per altre informazioni, vedere Pattern di progettazione Observer.

Usare un tipo separato per i dati di notifica

L'oggetto che contiene i dati inviati dal provider ai relativi osservatori corrisponde al parametro di tipo generico di IObservable<T> e IObserver<T>. Anche se questo oggetto può essere uguale all'implementazione IObservable<T> , definirlo come tipo separato. Un tipo di dati dedicato mantiene le responsabilità del provider separate dal payload di notifica e semplifica l'evoluzione dell'API.

Non basarsi sull'ordine di notifica

L'ordine in cui gli osservatori ricevono le notifiche non è definito. Il fornitore è libero di usare qualsiasi metodo per determinare l’ordine, quindi non scrivere osservatori che dipendono dall’essere notificati prima o dopo un altro osservatore.

Effettuare la sottoscrizione e eliminare thread-safe

In genere, un provider implementa il IObservable<T>.Subscribe metodo aggiungendo un osservatore a un elenco sottoscrittore rappresentato da un oggetto raccolta e implementa il IDisposable.Dispose metodo rimuovendo l'osservatore da tale elenco. Un osservatore può chiamare questi metodi in qualsiasi momento. Il contratto provider/osservatore non specifica chi è responsabile dell'annullamento della sottoscrizione dopo il IObserver<T>.OnCompleted metodo di callback, quindi il provider e l'osservatore potrebbero entrambi tentare di rimuovere lo stesso membro dall'elenco.

Per evitare condizioni di competizione, assicurarsi che i metodi Subscribe e Dispose siano sicuri per i thread. In genere, ciò comporta l'uso di una raccolta simultanea o di un blocco. Le implementazioni che non sono thread-safe devono documentare in modo esplicito che non lo sono.

Documentare eventuali garanzie di contratto aggiuntive

Specificare eventuali garanzie aggiuntive in un livello sopra il contratto provider/osservatore. Quando si impongono altri requisiti, chiamarli chiaramente in modo che gli utenti non siano confusi sul contratto osservatore.

Gestire le eccezioni come informative

A causa dell'accoppiamento libero tra un provider di dati e un osservatore, le eccezioni nel modello di progettazione osservatore sono destinate a essere informative. Questa caratteristica influisce sul modo in cui i provider e gli osservatori gestiscono le eccezioni.

Chiamare OnError solo quando gli aggiornamenti non possono continuare

Il OnError metodo è destinato a un messaggio informativo per gli osservatori, molto simile al IObserver<T>.OnNext metodo . Tuttavia, il OnNext metodo fornisce a un osservatore dati correnti o aggiornati, mentre il OnError metodo indica che il provider non può fornire dati validi.

Attenersi alle procedure consigliate seguenti quando si gestiscono le eccezioni e si chiama il metodo OnError:

  • Se presenta requisiti specifici, il provider deve gestire le proprie eccezioni.
  • Il provider non deve aspettarsi o richiedere che gli osservatori gestisca le eccezioni in modo particolare.
  • Il provider deve chiamare il OnError metodo quando gestisce un'eccezione che compromette la possibilità di fornire aggiornamenti. Passare informazioni su tali eccezioni all'osservatore. In altri casi, non è necessario notificare agli osservatori un'eccezione.

Dopo che il provider chiama il metodo OnError o IObserver<T>.OnCompleted, non devono esserci ulteriori notifiche e il provider può annullare la sottoscrizione dei propri osservatori. Tuttavia, gli osservatori possono anche annullare l’iscrizione in qualsiasi momento, sia prima sia dopo aver ricevuto una notifica OnError o IObserver<T>.OnCompleted. Il modello di progettazione dell'osservatore non determina se il provider o l'osservatore è responsabile dell'annullamento della sottoscrizione, quindi entrambi potrebbero tentare di annullare la sottoscrizione. In genere, quando gli osservatori annullano la sottoscrizione, vengono rimossi da una raccolta di sottoscrittori. In un'applicazione a thread singolo, l'implementazione IDisposable.Dispose deve garantire che un riferimento a un oggetto sia valido e che l'oggetto sia un membro dell'insieme sottoscrittori prima di tentare di rimuovere l'oggetto. In un'applicazione multithreading usare un blocco per proteggere la raccolta di osservatori.

Considerare le notifiche OnError come informative negli osservatori

Quando un osservatore riceve una notifica di errore da un provider, l'osservatore deve considerare l'eccezione come informativo e non deve eseguire alcuna azione specifica.

Segui queste buone pratiche quando rispondi a una chiamata al metodo OnError proveniente da un provider:

  • Non generare eccezioni da implementazioni dell'interfaccia come OnNext o OnError. Se l'osservatore solleva eccezioni, aspettati che queste eccezioni rimangano non gestite.
  • Per preservare lo stack di chiamate, un osservatore che desidera generare un oggetto Exception passato al metodo OnError deve racchiudere l'eccezione prima di generarla. Utilizzare un oggetto eccezione standard per questo scopo.

Non annullare la registrazione nel metodo Subscribe

Non tentare di annullare la registrazione nel IObservable<T>.Subscribe metodo perché potrebbe comportare un riferimento Null.

Collegare un osservatore a un singolo provider

Sebbene sia possibile collegare un osservatore a più provider, il modello consigliato consiste nel collegare un'istanza IObserver<T> a una IObservable<T> sola istanza.