My struggles in understanding and learning about Object Oriented design, and the tools and knowledge I've taken from them.

Friday, January 22, 2010

The Observer Design Pattern

I've mentioned before that I don't get too wrapped up in design patterns because of the tendency to overuse them, or to use the wrong one, resulting in overly-complicated code. There are, however, times when a design pattern is the exact solution for a given problem.

As an academic experiment, I recently looked into the "Observer" design pattern, which is probably one of the coolest of the design patterns (IMHO). The Observer design pattern is also called a "Publish-Subscribe" design pattern, because it creates a platform by which some entity can maintain a list of publishers and subscribers, and map them together. Then publishers and subscribers can send and receive messages, and have some level of confidence that the messages get to where they need to go.

The implementation of the Observer pattern I used was such that there was a managing "Engine" that kept track of all the subscriptions, and when it was notified of an event, it would notify all subscribers of that particular event. It was then the responsibility of the subscriber to either accept or disregard a message, based on the subject of the message.

The Observer pattern is not needed by all conceivable applications; in fact, most applications would not need it at all. So, one of the things I wanted to do was to think of a fun example of how one could use the Observer pattern. My solution: a Television analogy. A television has commercials. A television has people who watch it. Some of those people are shopping for the products that the commercials contain. For instance, some people are in the market to purchase furniture. Some people are in the market to purchase a car; therefore, the television is a medium for makers of those products to put commercials in them for watchers to watch. If a viewer of the television is not currently shopping for it, then they simply disregard the commercial.

One of the downfalls of the Observer pattern is that, because it is the role of the subscriber to either pay attention to or disregard a message, excess traffic gets generated. In my implementation of the Observer pattern, it is scalable to deal with this issue fairly easily, by switching this responsibility from the potential subscriber to the engine that manages the list.

Below are the list of classes I created, along with their definition:

Subject - An abstract class that other classes will implement. A subject, in this example, is the TV commercial subject. For instance, a Car Commercial would be of type CarCommercial, which inherits from Subject.


public abstract class Subject
{
   protected List<Observer> _listOfObservers;
   public string SubjectName;

   protected SubscriptionEngine _engine;

   public Subject(SubscriptionEngine connectingEngine)
   {
      _engine = connectingEngine;
   }

   public abstract void Attach(Observer o);

   public abstract void Detach(Observer o);

   public virtual void Notify()
   {
      foreach (Observer obs in _listOfObservers)
      {
         _engine.Notify(this);
      }
   }

   public abstract string State
   {
      get;
      set;
   }
      
}


CarCommercial - Concrete class that inherits from Subject, and represents one of the Subjects that the engine will know about.

public class CarCommercial : Subject
{
   private string _state;

   public CarCommercial()
   : base(null)
   {
      this.SubjectName = "Car Commercial";
   }

   public CarCommercial(SubscriptionEngine engine) : base(engine)
   {
      this.SubjectName = "Car Commercial";
   }

   public override string State
   {
      get { return _state; }
      set { _state = "Hello"; }
   }
   public override void Attach(Observer o)
   {
      _engine.Register(this, o);
   }

   public override void Detach(Observer o)
   {
      _engine.Unregister(this, o);
   }
}

FurnitureCommercial - Concrete class that inherits from Subject, and represents one of the Subjects that the engine will know about.

public class FurnitureCommercial : Subject
{
   private string _state;

   public FurnitureCommercial()
   : base(null)
   {
      this.SubjectName = "Furniture Commercial";
   }

   public FurnitureCommercial(SubscriptionEngine engine)
   : base(engine)
   {
      this.SubjectName = "Furniture Commercial";
   }

   public override string State
   {
      get { return _state; }
      set { _state = value; }
   }

   public override void Attach(Observer o)
   {
      _engine.Register(this, o);
   }

   public override void Detach(Observer o)
   {
      _engine.Unregister(this, o);
   }
}

Observer - An abstract class that represents a television viewer, in this example. An observer, in the code created, can be either a CarShopper or a FurnitureShopper

public abstract class Observer
{
   protected SubscriptionEngine _engine;

   public string Name;

   public List<Subject> ListOfSubjectsSubscribedTo;

   public Observer(SubscriptionEngine engine)
   {
      ListOfSubjectsSubscribedTo = new List<Subject>();
      _engine = engine;
   }

   public abstract bool Update(Subject s);
}

CarShopper - Concrete class that implements Observer. Represents a TV Viewer who cares about CarCommercials

public class CarShopper : Observer
{
   public CarShopper(SubscriptionEngine e) : base(e)
   {
      ListOfSubjectsSubscribedTo.Add(new CarCommercial());
      this.Name = "Car Shopper";
      _engine.Register(ListOfSubjectsSubscribedTo[0], this);
   }

   public override bool Update(Subject s)
   {
      foreach (Subject subject in this.ListOfSubjectsSubscribedTo)
      {
         if (subject.SubjectName == s.SubjectName)
         {
            return true;
         }
      }
      return false;
   }
}

FurnitureShopper - Concrete class that implements Observer. Represents a TV Viewer who cares about FurnitureCommercials.

public class FurnitureShopper : Observer
{
   public FurnitureShopper(SubscriptionEngine e) : base(e)
   {
      this.Name = "Furniture Shopper";
      ListOfSubjectsSubscribedTo.Add(new FurnitureCommercial());
      _engine.Register(ListOfSubjectsSubscribedTo[0], this);
   }

   public override bool Update(Subject s)
   {
      foreach (Subject subject in this.ListOfSubjectsSubscribedTo)
      {
         if (subject.SubjectName == s.SubjectName)
         {
            return true;
         }
      }
      return false;
   }
}

Subscription - Class (could have easily been a Struct) that represents a mapping between an Observer (TV Watcher) and Subject (TV Commercial)

public class Subscription
{
   private Subject _subject;
   private Observer _observer;

   public Subscription(Subject s, Observer o)
   {
      _subject = s;
      _observer = o;
   }

   public Subject Subject_
   {
      get { return _subject; }
   }

   public Observer Observer_
   {
      get { return _observer; }
   }
}

SubscriptionEngine - The engine that maintains a list of Subscriptions, and handles the actual notification, in the event that a Subject is sent. The way this is implemented is in it's public method Notify(). The parameter in Notify is a Subject. So therefore, the SubscriptionEngine would go through its list of subscribers, and send to all Observers. It would be the Observer's responsibility of whether or not they care.

public class SubscriptionEngine
{
   List<Subscription> _listOfSubscriptions;

   public SubscriptionEngine()
   {
      _listOfSubscriptions = new List<Subscription>();
   }

   public bool Contains(Subscription item)
   {
      foreach (Subscription subscription in _listOfSubscriptions)
      {
         if (subscription.Observer_.Name == item.Observer_.Name
         && subscription.Subject_.SubjectName == item.Subject_.SubjectName)
            return true;
      }
      return false;
   }

   public void Register(Subject s, Observer o)
   {
      Subscription item = new Subscription(s, o);
      if (!this.Contains(item))
      _listOfSubscriptions.Add(new Subscription(s, o));
   }

   public void Unregister(Subject s, Observer o)
   {
      for (int i = 0; i < _listOfSubscriptions.Count; i++)
      {
         if (_listOfSubscriptions[i].Observer_ == o && _listOfSubscriptions[i].Subject_ == s)
            _listOfSubscriptions.RemoveAt(i);
      }
   }

   public void Notify(Subject s)
   {
      foreach (Subscription subscription in _listOfSubscriptions)
      {
         if (subscription.Observer_.Update(s))
            MessageBox.Show(s.SubjectName + " has sent a message to " + subscription.Observer_.Name);
      }
   }
}

Television - A simple Form, which acts as an intermediary between the SubscriptionEngine and the Subjects and Observers.

public partial class Television : Form
{
   SubscriptionEngine _engine;

   public Television()
   {
      _engine = new SubscriptionEngine();
      InitializeComponent();
   }

   private void onTelevisionLoad(object sender, EventArgs e)
   {
      this.showCommercials();
   }

   private void showCommercials()
   {
      FurnitureCommercial furnitureCommercial = new FurnitureCommercial(_engine);
      CarCommercial carCommercial = new CarCommercial(_engine);

      FurnitureShopper furnitureShopper = new FurnitureShopper(_engine);
      CarShopper carShopper = new CarShopper(_engine);

      furnitureCommercial.Attach(furnitureShopper);
      carCommercial.Attach(carShopper);

      _engine.Notify(furnitureCommercial);
      _engine.Notify(carCommercial);
   }
}

The way I see the Observer pattern working is a little bit different that it was laid out by the Gang Of Four, or other authors. In the GOF's implementation, the Subject has more "smarts" than I think it needs. The way I see this pattern, the Subject ought to be rather "dumb," and the responsibility for doing stuff should lie with the Observer and the SubscriptionEngine.

For more info about the Observer pattern, check out http://dofactory.com/Patterns/PatternObserver.aspx

No comments:

Followers

Search This Blog

Powered by Blogger.