The Observer design pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
C# code examples of the Observer design pattern is provided in 3 forms:
A visualization of the classes and objects participating in this pattern.
The classes and objects participating in this pattern include:
Stock
)
IBM
)
IInvestor
)
Investor
)
This structural code demonstrates the Observer pattern in which registered objects are notified of and updated with a state change.
using System;
using System.Collections.Generic;
namespace Observer.Structural
{
/// <summary>
/// Observer Design Pattern
/// </summary>
public class Program
{
public static void Main(string[] args)
{
// Configure Observer pattern
ConcreteSubject s = new ConcreteSubject();
s.Attach(new ConcreteObserver(s, "X"));
s.Attach(new ConcreteObserver(s, "Y"));
s.Attach(new ConcreteObserver(s, "Z"));
// Change subject and notify observers
s.SubjectState = "ABC";
s.Notify();
// Wait for user
Console.ReadKey();
}
}
/// <summary>
/// The 'Subject' abstract class
/// </summary>
public abstract class Subject
{
private List<Observer> observers = new List<Observer>();
public void Attach(Observer observer)
{
observers.Add(observer);
}
public void Detach(Observer observer)
{
observers.Remove(observer);
}
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
public class ConcreteSubject : Subject
{
private string subjectState;
// Gets or sets subject state
public string SubjectState
{
get { return subjectState; }
set { subjectState = value; }
}
}
/// <summary>
/// The 'Observer' abstract class
/// </summary>
public abstract class Observer
{
public abstract void Update();
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
public class ConcreteObserver : Observer
{
private string name;
private string observerState;
private ConcreteSubject subject;
// Constructor
public ConcreteObserver(
ConcreteSubject subject, string name)
{
this.subject = subject;
this.name = name;
}
public override void Update()
{
observerState = subject.SubjectState;
Console.WriteLine("Observer {0}'s new state is {1}",
name, observerState);
}
// Gets or sets subject
public ConcreteSubject Subject
{
get { return subject; }
set { subject = value; }
}
}
}
This real-world code demonstrates the Observer pattern in which registered investors are notified every time a stock changes value.
using System;
using System.Collections.Generic;
namespace Observer.RealWorld
{
/// <summary>
/// Observer Design Pattern
/// </summary>
public class Program
{
public static void Main(string[] args)
{
// Create IBM stock and attach investors
IBM ibm = new IBM("IBM", 120.00);
ibm.Attach(new Investor("Sorros"));
ibm.Attach(new Investor("Berkshire"));
// Fluctuating prices will notify investors
ibm.Price = 120.10;
ibm.Price = 121.00;
ibm.Price = 120.50;
ibm.Price = 120.75;
// Wait for user
Console.ReadKey();
}
}
/// <summary>
/// The 'Subject' abstract class
/// </summary>
public abstract class Stock
{
private string symbol;
private double price;
private List<IInvestor> investors = new List<IInvestor>();
// Constructor
public Stock(string symbol, double price)
{
this.symbol = symbol;
this.price = price;
}
public void Attach(IInvestor investor)
{
investors.Add(investor);
}
public void Detach(IInvestor investor)
{
investors.Remove(investor);
}
public void Notify()
{
foreach (IInvestor investor in investors)
{
investor.Update(this);
}
Console.WriteLine("");
}
// Gets or sets the price
public double Price
{
get { return price; }
set
{
if (price != value)
{
price = value;
Notify();
}
}
}
// Gets the symbol
public string Symbol
{
get { return symbol; }
}
}
/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
public class IBM : Stock
{
// Constructor
public IBM(string symbol, double price)
: base(symbol, price)
{
}
}
/// <summary>
/// The 'Observer' interface
/// </summary>
public interface IInvestor
{
void Update(Stock stock);
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
public class Investor : IInvestor
{
private string name;
private Stock stock;
// Constructor
public Investor(string name)
{
this.name = name;
}
public void Update(Stock stock)
{
Console.WriteLine("Notified {0} of {1}'s " +
"change to {2:C}", name, stock.Symbol, stock.Price);
}
// Gets or sets the stock
public Stock Stock
{
get { return stock; }
set { stock = value; }
}
}
}
The .NET optimized code demonstrates the same code as above but uses
more modern C# and .NET features.
Here is an elegant C# Observer solution.
namespace Observer.NetOptimized;
using static System.Console;
using System;
/// <summary>
/// Observer Design Pattern
/// </summary>
public class Program
{
public static void Main()
{
// Create IBM stock and attach investors
var ibm = new IBM(120.00);
// Attach 'listeners', i.e. Investors
ibm.Attach(new Investor { Name = "Sorros" });
ibm.Attach(new Investor { Name = "Berkshire" });
// Fluctuating prices will notify listening investors
ibm.Price = 120.10;
ibm.Price = 121.00;
ibm.Price = 120.50;
ibm.Price = 120.75;
// Wait for user
ReadKey();
}
}
// Custom event arguments
public class ChangeEventArgs : EventArgs
{
// Gets or sets symbol
public string Symbol { get; set; }
// Gets or sets price
public double Price { get; set; }
}
/// <summary>
/// The 'Subject' abstract class
/// </summary>
public abstract class Stock(string symbol, double price)
{
protected string symbol = symbol;
protected double price = price;
// Event
public event EventHandler<ChangeEventArgs> Change = null!;
// Invoke the Change event
public virtual void OnChange(ChangeEventArgs e)
{
Change?.Invoke(this, e);
}
public void Attach(IInvestor investor)
{
Change += investor.Update;
}
public void Detach(IInvestor investor)
{
Change -= investor.Update;
}
// Gets or sets the price
public double Price
{
get => price;
set
{
if (price != value)
{
price = value;
OnChange(new ChangeEventArgs { Symbol = symbol, Price = price });
WriteLine("");
}
}
}
}
/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
public class IBM(double price) : Stock("IBM", price)
{
}
/// <summary>
/// The 'Observer' interface
/// </summary>
public interface IInvestor
{
void Update(object sender, ChangeEventArgs e);
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
public class Investor : IInvestor
{
// Gets or sets the investor name
public string Name { get; set; } = null!;
// Gets or sets the stock
public Stock Stock { get; set; } = null!;
public void Update(object sender, ChangeEventArgs e)
{
WriteLine("Notified {0} of {1}'s " +
"change to {2:C}", Name, e.Symbol, e.Price);
}
}