- It defines a one-to-many dependency between objects so when one object changes state, all its dependents are notified and updated automatically.
- The usage is to define subjects that must change the state of observers.
- Subject relies on a list of observers.
- The subjects and observers are "loosely coupled" because they interact which makes them coupled but also have a little knowledge about each other.
- Events and Delegates, which are first class language features, act as the Subject and Observers respectively as defined in the Observer pattern.
- The Observer pattern facilitates good object-oriented designs as it promotes loose coupling.
- Observers register and unregister themselves with subjects that maintain a list of interested observers.
- The subject does not depend on any particular observer, as long as the delegates are of the correct type for the event.
Participants
Subject
(IStock)
- Knows its observers.
- Any number of Observer objects may observe a subject.
- Provides an interface for attaching and detaching Observer objects.
ConcreteSubject
(MicrosoftStock)
- 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.
var microsoftStock = new MicrosoftStock();
microsoftStock.Symbol = "MS";
var investor1 = new Investor();
investor1.Name = "Bill Gates";
var investor2 = new Investor();
investor2.Name = "Berkshire";
microsoftStock.Attach(investor1);
microsoftStock.Attach(investor2);
microsoftStock.Price = 120.10;
microsoftStock.Price = 121.00;
microsoftStock.Price = 120.50;
microsoftStock.Price = 120.75;
public interface IStock
{
List<IInvestor> Investors { get; }
string Symbol { get; set; }
double Price { get; set; }
void Attach(IInvestor investor);
void Detach(IInvestor investor);
void Notify();
}
public class MicrosoftStock : IStock
{
public List<IInvestor> Investors { get; private set; } = new List<IInvestor>();
public string Symbol { get; set; }
private double _price;
public double Price
{
get => _price;
set
{
if (_price != value)
{
_price = value;
Notify();
}
}
}
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);
}
}
public interface IInvestor
{
string Name { get; set; }
void Update(IStock stock);
}
public class Investor : IInvestor
{
public string Name { get; set; }
public void Update(IStock stock) => Console.WriteLine("Notified {0} of {1}'s change to {2:C}", Name, stock.Symbol, stock.Price);
}
Rules of Thumb
- Chain of Responsibility, Command, Mediator, and Observer, address how you can decouple senders and receivers, but with different trade-offs.
- Chain of Responsibility passes a sender request along a chain of potential receivers.
- Command normally specifies a sender-receiver connection with a subclass.
- Mediator has senders and receivers reference each other indirectly.
- Observer defines a very decoupled interface that allows for multiple receivers to be configured at run-time.
- Mediator and Observer are competing patterns.
- The difference between them is that Observer distributes communication by introducing "observer" and "subject" objects, whereas a Mediator object encapsulates the communication between other objects.
- It is easier to make reusable Observers and Subjects than to make reusable Mediators.
- On the other hand, Mediator can leverage Observer for dynamically registering colleagues and communicating with them.