Observer
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:
using System.Collections.Generic;
namespace DesignPatterns.Observer
{
// typical structure of an observable
public interface IObservable
{
public List<IObserver> Observers { get; set; }
public void Register(IObserver observer);
public void Unregister(IObserver observer);
public void Notify();
}
// typical structure of an observer
public interface IObserver
{
public void OnNotify();
}
}
Definition of Classes
An example implementation is seen below:
using System;
using System.Collections.Generic;
namespace DesignPatterns.Observer
{
// typical structure of an observable
public interface IObservable<T>
{
// register an object as an observer
public void Register(T observer);
//unregister an object as an observer
public void Unregister(T observer);
// notify observers
public void Notify();
}
// typical structure of an observer, implement IDisposable for cleanup
public interface IObserver<T>: IDisposable
{
// handle notification event from observable
public void OnNotify(T observable);
}
public class BookingStatus : IObservable<User>
{
// keep track of the observer list
private List<User> _observers = new List<User>();
// status, we will notify on changes from this
private string _status;
public string Status
{
get =>_status;
}
public void UpdateStatus (string status)
{
_status = status;
// run notification function when change happens
Notify();
}
public void Notify()
{
// notify all observers
_observers.ForEach(o => o.OnNotify(this));
}
public void Register(User user)
{
if (!_observers.Contains(user))
{
_observers.Add(user);
}
}
public void Unregister(User user)
{
if (_observers.Contains(user))
{
_observers.Remove(user);
}
}
}
public class User : IObserver<BookingStatus>
{
public string Name { get; }
// store a reference to observable for deregistration
private BookingStatus _observable { get; set; }
public User(string name, BookingStatus observable)
{
Name = name;
_observable = observable;
_observable.Register(this);
}
// handle notification
public void OnNotify(BookingStatus observable)
{
Console.WriteLine($"User {Name} Status Update: {observable.Status}");
}
public void Dispose()
{
// unregister when disposed
_observable.Unregister(this);
}
}
}
Usage
// instantiate observable
var bookingStatus = new BookingStatus();
// instantiate observers
var user1 = new User("user1", bookingStatus);
var user2 = new User("user2", bookingStatus);
// update status, notify all observers
bookingStatus.UpdateStatus("notification to user1 and user2");
// dispose user1
user1.Dispose();
// this will only notify user1
bookingStatus.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