Design patterns: Observer

Design patterns (4 part series)

  1. Design patterns: Singleton
  2. Design patterns: Factory
  3. Design patterns: Observer
  4. Design patterns: Decorator

Wassup! This is the third post that I’m writing while learning design patterns. My first two posts covered creational design patterns. This time we’ll be taking a look at the observer pattern that is a behavioral pattern. I’ll first give a definition of the pattern, then show its implementation in C#, and finish with a code example.

Overview

  1. Definition
  2. Implementation
  3. Example

Definition

Observer: a thing that watches or notices something.

The observer pattern allows us to create a one-to-many relationship between objects so that when one object changes, the many objects observing it will be notified and update accordingly.

In this pattern, there is one subject and many observers. The subject holds whatever state is of interest and notifies the observers when the state changes. Each observer keeps a reference to the subject, stores the state that should be consistent with the subject’s, and implements the observer updating the interface to keep the state consistent with that of the subject.

Implementation

To achieve the observer pattern, there are four types of classes we need to implement:

  1. Subject

    • Stores a List of its Observer objects.
    • Defines the Attach, Detach, and Notify methods.
    • Notifies the observers when the state changes.
  2. ConcreteSubject

    • Holds the state.
  3. Observer

    • Defines an Update interface.
  4. ConcreteObserver

    • Implements the Update interface.
    • Stores reference to the ConcreteSubject.
    • Stores state that should stay consistent with the ConcreteSubject.

Using C#, the pattern looks like this:

using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer = new ConcreteObserver(subject);
subject.Attach(observer);
subject.SomeState = 100;
subject.Notify();
}
}
// 1. Subject
abstract class Subject
{
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 (var observer in this.observers)
{
observer.Update();
}
}
}
// 2. Concrete Subject
class ConcreteSubject : Subject
{
public int SomeState { get; set; }
}
// 3. Observer
abstract class Observer
{
public abstract void Update();
}
// 4. Concrete Observer
class ConcreteObserver : Observer
{
public int ObserverState { get; set; }
ConcreteSubject MySubject { get; set; }
public ConcreteObserver(ConcreteSubject subject)
{
this.MySubject = subject;
}
public override void Update()
{
this.ObserverState = MySubject.SomeState;
Console.WriteLine("My state has been updated: " + this.ObserverState);
}
}

Example

There are all sorts of reasons why we may want to use a subject-observer relationship while developing; this pattern is used a lot. Some examples include UI components reacting to one another, subscription services, app users following other users, etc.

Let’s write a program that models sports fans cheering for their favorite player. The subject will be the athlete and the observers will be the fans. Every time the athlete scores, the fans will react to it in some way.

using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
BasketballPlayer damianLillard = new BasketballPlayer("Damian Lillard");
BasketballFan basketballFan1 = new BasketballFan(damianLillard);
damianLillard.Attach(basketballFan1);
damianLillard.Score(3);
BasketballFan basketballFan2 = new BasketballFan(damianLillard);
damianLillard.Attach(basketballFan2);
damianLillard.Score(2);
}
abstract class Player
{
List<Fan> fans = new List<Fan>();
public void Attach(Fan f)
{
fans.Add(f);
}
public void Detach(Fan f)
{
fans.Remove(f);
}
public void Notify()
{
foreach(Fan f in fans)
{
f.Update();
}
}
}
class BasketballPlayer : Player
{
public string Name;
public int Points = 0;
public BasketballPlayer(string name)
{
this.Name = name;
}
public void Score(int points)
{
this.Points += points;
this.Notify();
}
}
abstract class Fan
{
public abstract void Update();
}
class BasketballFan : Fan
{
BasketballPlayer FavoritePlayer;
int FavoritePlayerPoints;
public BasketballFan(BasketballPlayer player)
{
this.FavoritePlayer = player;
this.FavoritePlayerPoints = this.FavoritePlayer.Points;
}
public override void Update()
{
this.FavoritePlayerPoints = this.FavoritePlayer.Points;
Console.WriteLine(this.FavoritePlayer.Name + " has " + this.FavoritePlayerPoints + " points!");
}
}
}

Disclaimer: There are a lot of resources for learning design patterns, and they can be implemented in different ways. I would recommend exploring more resources when finished with this post.

Attributions:
  1. undefined by undefined