Observer
Updated: 03 September 2023
The Observer pattern defines a one to many dependency between objects so that when one object changes state, all it’s dependencies are notfied of this change
The Observer pattern is used to enable one object to subscribe to some changes of another object. It allows us to move from a poll
type architecture to a push
type architecture
This pattern allows a client (observer) to subscribe to messages/changes from a subject (an observarble), provided the observable is made aware of all observers
Example
A broad idea of the what an observable and observer would contain may look something like this:
1using System.Collections.Generic;2
3namespace DesignPatterns.Observer4{5 // typical structure of an observable6 public interface IObservable7 {8 public List<IObserver> Observers { get; set; }9 public void Register(IObserver observer);10 public void Unregister(IObserver observer);11 public void Notify();12 }13
14 // typical structure of an observer15 public interface IObserver16 {17 public void OnNotify();18 }19}
Definition of Classes
An example implementation is seen below:
1using System;2using System.Collections.Generic;3
4namespace DesignPatterns.Observer5{6 // typical structure of an observable7 public interface IObservable<T>8 {9 // register an object as an observer10 public void Register(T observer);11 //unregister an object as an observer12 public void Unregister(T observer);13 // notify observers14 public void Notify();15 }16
17 // typical structure of an observer, implement IDisposable for cleanup18 public interface IObserver<T>: IDisposable19 {20 // handle notification event from observable21 public void OnNotify(T observable);22 }23
24 public class BookingStatus : IObservable<User>25 {26 // keep track of the observer list27 private List<User> _observers = new List<User>();28
29 // status, we will notify on changes from this30 private string _status;31
32 public string Status33 {34 get =>_status;35 }36
37 public void UpdateStatus (string status)38 {39 _status = status;40 // run notification function when change happens41 Notify();42 }43
44 public void Notify()45 {46 // notify all observers47 _observers.ForEach(o => o.OnNotify(this));48 }49
50 public void Register(User user)51 {52 if (!_observers.Contains(user))53 {54 _observers.Add(user);55 }56 }57
58 public void Unregister(User user)59 {60 if (_observers.Contains(user))61 {62 _observers.Remove(user);63 }64 }65 }66
67 public class User : IObserver<BookingStatus>68 {69 public string Name { get; }70 // store a reference to observable for deregistration71 private BookingStatus _observable { get; set; }72
73 public User(string name, BookingStatus observable)74 {75 Name = name;76 _observable = observable;77 _observable.Register(this);78 }79
80 // handle notification81 public void OnNotify(BookingStatus observable)82 {83 Console.WriteLine($"User {Name} Status Update: {observable.Status}");84 }85
86 public void Dispose()87 {88 // unregister when disposed89 _observable.Unregister(this);90 }91 }92}
Usage
1// instantiate observable2var bookingStatus = new BookingStatus();3
4// instantiate observers5var user1 = new User("user1", bookingStatus);6var user2 = new User("user2", bookingStatus);7
8// update status, notify all observers9bookingStatus.UpdateStatus("notification to user1 and user2");10
11// dispose user112user1.Dispose();13
14// this will only notify user115bookingStatus.UpdateStatus("notification to user1 only");
Using this kind of framework, we can have other classes that extend the IObserver
and by creating and registering these we can have different classes that all react to the Notify
function calls
In a real implementation you may want to consider using the C# built-in implementation, this makes use of a lot of additional functionality like correct instance disposing, etc. Information on that can be found in the docs
Implementations can differ between different languages/frameworks, you can view implementations in different languages on Refactoring Guru