Dec 1, 2011

Observer Pattern - Don't call us, we'll call you!

This is used whenever one or more objects (referred as observers) need to be notified when things change to the other object (referred as subject).

Simplest Solution 
Have subject notify observer when its data change. In this case you will pass reference of the observer class to the subject, so whenever subject wants to notify it can call objerver's notify me

 public class Subject
 {
  private Observer _observer;

  public Subject(Observer observer)
  {
   _observer = observer;
  } 

  public void DoWork(string desc)
  {
   
   Console.WriteLine( "Working on " + desc + "..." );
   Thread.Sleep( 3000 );
   Console.WriteLine( "Finished " + desc );
   _observer.NotifyOfWork( desc );
   Console.WriteLine();
   Thread.Sleep( 1000 );
  }
 }
 
 public class Observer
 {
  internal void NotifyOfWork(string desc)
  {
   Console.WriteLine( "Great " + desc + " is done" );
  }
 }
 
 public class Program
 {
  private static void Main()
  {
   Observer observer = new Observer();
   Subject subject = new Subject( observer);
   subject.DoWork( "Do some work" );
   subject.DoWork( "Do some more work" );
  }

 }

Issue with the above solution 
In the above solution Subject class is very tightly coupled with Observer. We have to add new code to add a new Observer. Also Observer cannot be added or removed dynamically.

Decoupling Subject and Observer
 


 public class Subject
 {
  private List observers = new List();

  public void AddObserver(IObserver obs)
  {
  observers.Add( obs );
  }

  public void RemoveObserver(Observer observer)
  {
  observers.Remove(observer);
  }

  public void Notify(string desc)
  {
  // Notify the observers by looping through all the registered observers.
  foreach(IObserver observer in observers)
   {
   observer.NotifyOfWork(desc);
   }
  }

  public void DoWork(string desc)
  {
   Console.WriteLine( "Working on " + desc + "..." );
   Thread.Sleep( 3000 );
   Console.WriteLine( "Finished " + desc );

   Notify( desc );

   Console.WriteLine();
   Thread.Sleep( 1000 );
  }
 }

 public class Observer1 : IObserver
 {
  internal void NotifyOfWork(string desc)
  {
   Console.WriteLine( "Great " + desc + " is done. This is notification from Observer1" );
  }
 }

 public class Observer2 : IObserver
 {
  internal void NotifyOfWork(string desc)
  {
   Console.WriteLine( "Great " + desc + " is done. This is notification from Observer2" );
  }
 } 
 
 public interface IObserver
 {
  void NotifyOfWork(string desc);
 }

 public class Program
 {
  private static void Main()
  {
   Observer1 observer1 = new Observer1();
   Observer2 observer2 = new Observer2();

   Subject subject = new Subject();
   subject.AddObserver( observer1 );
   subject.AddObserver( observer2 );

   subject.DoWork( "Do some work" );
   subject.DoWork( "Do some more work" );
  }
 }

In the above solution there is abstract coupling between Subject and Observer. Subject doesn't know the concrete class of any observer. We can add new observer without modifying subject. The notification that subject sends needn't specify the receiver. The notification is broadcast to all interested object that subscribed to it. The subject doesn't care how many interested objects exist; its only responsibility is to notify its observers. This gives you the freedom to add and remove observers at any time. It's up to the observer to handle or ignore a notification. So the solution is open for extension, but closed for modification. "O" (open/closed principle) in "SOLID".

Issue with the previous solution 
All my observer need to implement a common Interface. Also we have to write code to add/remove observer.

Even Cleaner Solution


 public delegate void WorkCompletedDelegate(string desc);

 public class Subject
 {
  public event WorkCompletedDelegate WorkCompleted;

  public void DoWork(string desc)
  {
   Console.WriteLine( "Working on " + desc + "..." );
   Thread.Sleep( 3000 );
   Console.WriteLine( "Finished " + desc );
   if (WorkCompleted != null)
   {
    WorkCompleted( desc );
   }
   Console.WriteLine();
   Thread.Sleep( 1000 );
  }
 }
 
 public class Observer1 
 {
  internal void NotifyOfWork(string desc)
  {
   Console.WriteLine( "Great " + desc + " is done. This is notification from Observer1" );
  }
 }
 
 public class Observer2 
 {
  internal void SubjectIsDone(string desc)
  {
   Console.WriteLine( "Great " + desc + " is done. This is notification from Observer2" );
  }
 }
 
 public class Program
 {
  private static void Main()
  {
   Observer1 observer1 = new Observer1();
   Observer2 observer2 = new Observer2();

   Subject subject = new Subject();
   subject.WorkCompleted = observer1.NotifyOfWork;
   subject.WorkCompleted = observer2.SubjectIsDone;

   subject.DoWork( "Do some work" );
   subject.DoWork( "Do some more work" );
  }
 }
 

No comments:

Post a Comment