Observer¶
- Observer
- UML class diagram
- Participants
- Structural code in C# .{10}
- Real-world code in C# .{10}
- .NET Optimized code in C# .{10}
Summary: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Frequency of use: High
UML class diagram¶
Participants¶
The classes and objects participating in this pattern are:
- Subject (
Stock
) - knows its observers. Any number of Observer objects may observe a subject
- provides an interface for attaching and detaching Observer objects.
- ConcreteSubject (
IBM
) - stores state of interest to ConcreteObserver
- sends a notification to its observers when its state changes
- Observer (
IInvestor
) - defines an updating interface for objects that should be notified of changes in a subject.
- ConcreteObserver (
Investor
) - maintains a reference to a ConcreteSubject object
- stores state that should stay consistent with the subject's
- implements the Observer updating interface to keep its state consistent with the subject's
Structural code in C# .{10}¶
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 DoFactory.GangOfFour.Observer.Structural
{
/// <summary>
/// MainApp startup class for Structural
/// Observer Design Pattern.
/// </summary>
class MainApp
{
/// <summary>
/// Entry point into console application.
/// </summary>
static void Main()
{
// 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>
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>
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>
abstract class Observer
{
public abstract void Update();
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
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; }
}
}
}
Output¶
Real-world code in C# .{10}¶
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 DoFactory.GangOfFour.Observer.RealWorld
{
/// <summary>
/// MainApp startup class for Real-World
/// Observer Design Pattern.
/// </summary>
class MainApp
{
/// <summary>
/// Entry point into console application.
/// </summary>
static void Main()
{
// 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>
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>
class IBM : Stock
{
// Constructor
public IBM(string symbol, double price)
: base(symbol, price)
{
}
}
/// <summary>
/// The 'Observer' interface
/// </summary>
interface IInvestor
{
void Update(Stock stock);
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
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; }
}
}
}
.NET Optimized code in C# .{10}¶
using System;
using System.Collections.Generic;
namespace DoFactory.GangOfFour.Observer.NETOptimized
{
/// <summary>
/// MainApp startup class for .NET optimized
/// Observer Design Pattern.
/// </summary>
class MainApp
{
/// <summary>
/// Entry point into console application.
/// </summary>
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
Console.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>
abstract class Stock
{
protected string _symbol;
protected double _price;
// Constructor
public Stock(string symbol, double price)
{
this._symbol = symbol;
this._price = price;
}
// Event
public event EventHandler<ChangeEventArgs> Change;
// Invoke the Change event
public virtual void OnChange(ChangeEventArgs e)
{
if (Change != null)
{
Change(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 { return _price; }
set
{
if (_price != value)
{
_price = value;
OnChange(new ChangeEventArgs { Symbol = _symbol, Price = _price });
Console.WriteLine("");
}
}
}
}
/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
class IBM : Stock
{
// Constructor - symbol for IBM is always same
public IBM(double price)
: base("IBM", price)
{
}
}
/// <summary>
/// The 'Observer' interface
/// </summary>
interface IInvestor
{
void Update(object sender, ChangeEventArgs e);
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
class Investor : IInvestor
{
// Gets or sets the investor name
public string Name { get; set; }
// Gets or sets the stock
public Stock Stock { get; set; }
public void Update(object sender, ChangeEventArgs e)
{
Console.WriteLine("Notified {0} of {1}'s " +
"change to {2:C}", Name, e.Symbol, e.Price);
}
}
}