GoF book says that "Observer pattern should defina a one-to-many dependency between objects so that when one object changes state, all its dependenst are notified and updated automatically". Subscribing to RSS feeds is a nice analogy. You subscribe to RSS feeds to show interest and you become an observer who demand for notification and RSS feeds become the subject and are responsable for providing information to all subscribers. I think this bit of information describing the pattern is enough, now lets see how we implement observer pattern.
Lets assume that we are developing a car controller software. One of the requirements is "Provide visual warning to the driver if seat belts are not locked". To meet the requirement we design
- SeatBelt class which is the subject. This class should notify interested objects about the state changes
- SeatBeltMonitor class is our observer. This class monitors for status change messages provided by the SeatBelt and displays messages
Implementing Observer Pattern in conventional way
Download GoF_Patterns_Observer1.rar (20,36 kb)
It is suggested to specify an interface or abstract classes when dealing with patterns so that the objects we create will adhere to some predefined contract. So we start our implementation by specifying two interfaces ISubject and IObserver
001 public interface ISubject
002
003 {
004
005 void RegisterObserver(IObserver observer);
006
007 void RemoveObserver(IObserver observer);
008
009 }
010
011
012
013 public interface IObserver
014
015 {
016
017 void Update(bool locked);
018
019 }
As I've mentioned above our SeatBelt class is the subject (source) of the state change and should implement the contract so that interested parties shall define their interest to the state changes or stop receiving state change notification. Here is our SeatBelt code
001 public class SeatBelt:ISubject
002
003 {
004
005 private IList<IObserver> _observers = new List<IObserver>();
006
007
008
009 private bool _locked = false;
010
011 public bool Locked
012
013 {
014
015 get { return _locked; }
016
017 set { _locked = value; UpdateObservers(); }
018
019 }
020
021
022
023
024
025 private void UpdateObservers()
026
027 {
028
029 foreach (IObserver observer in _observers)
030
031 {
032
033 try
034
035 {
036
037 observer.Update(_locked);
038
039 }
040
041 catch (Exception ex)
042
043 {
044
045 Console.WriteLine("Can not update observer. Error:" + ex.Message);
046
047 }
048
049 }
050
051 }
052
053
054
055 #region IObserver Members
056
057
058
059 public void RegisterObserver(IObserver observer)
060
061 {
062
063 if (observer == null || _observers.Contains(observer))
064
065 return;
066
067
068
069 _observers.Add(observer);
070
071 }
072
073
074
075 public void RemoveObserver(IObserver observer)
076
077 {
078
079 if (observer == null || !_observers.Contains(observer))
080
081 return;
082
083
084
085 _observers.Remove(observer);
086
087 }
088
089
090
091 #endregion
092
093 }
When value of the Locked property changes all registered observers will be notified. Our SeatBeltMonitor class simply responds to the change with a message. Here is our observer code
001 public class SeatBeltMonitor:IObserver
002
003 {
004
005 #region IObserver Members
006
007
008
009 public void Update(bool locked)
010
011 {
012
013 if (!locked)
014
015 Console.WriteLine("Please lock your seat belt!");
016
017 else
018
019 Console.WriteLine("Seat belt OK.");
020
021 }
022
023 #endregion
024
025 }
001 static void Main(string[] args)
002
003 {
004
005 SeatBelt seatBelt = new SeatBelt();
006
007 SeatBeltMonitor monitor = new SeatBeltMonitor();
008
009
010
011 seatBelt.RegisterObserver(monitor);
012
013 // Prints OK.
014
015 seatBelt.Locked = true;
016
017
018
019 //Prints warning
020
021 seatBelt.Locked = false;
022
023
024
025 seatBelt.RemoveObserver(monitor);
026
027 // Nothing will be printed
028
029 seatBelt.Locked = true;
030
031
032
033
034
035 Console.Read();
036
037 }
Implementing Observer Pattern with .NET Events
Download GoF_Patterns_Observer2.rar (20,72 kb)
.NET and C# provides a very flexible implementation of Observer Pattern through events and delegates.Here is the code for the alternative implementation. Some differences are
- We do not need to define contracts through ISubject and IObserver interfaces
- We specify the contract through delegate definitions (SeatBeltStatusChangedDelegate)
- We use custom classes as convention to pass status data from subject to the observers (SeatBeltEventArgs class)
- We register method pointers through events of the subject rather than registering our observer objects to our subject. In this case method signature is used as contract
- We use overloaded + and - operators for registration
001 public class SeatBeltEventArgs : EventArgs
002
003 {
004
005 public bool Locked { get; set; }
006
007 }
008
009
010
011 public class SeatBelt
012
013 {
014
015 private bool _locked = false;
016
017 public bool Locked
018
019 {
020
021 get { return _locked; }
022
023 set { _locked = value; FireStatusChangeEvent(); }
024
025 }
026
027
028
029 private SeatBeltStatusChangedDelegate _statusChanged;
030
031 public event SeatBeltStatusChangedDelegate StatusChanged
032
033 {
034
035 add { _statusChanged += value; }
036
037 remove { _statusChanged -= value; }
038
039 }
040
041
042
043
044
045 private void FireStatusChangeEvent()
046
047 {
048
049 if (_statusChanged == null)
050
051 return;
052
053
054
055 Delegate[] invokeList = _statusChanged.GetInvocationList();
056
057 foreach (SeatBeltStatusChangedDelegate d in invokeList)
058
059 {
060
061 SeatBeltEventArgs args = new SeatBeltEventArgs();
062
063 args.Locked = _locked;
064
065 try
066
067 {
068
069 d.Invoke(this, args);
070
071 }
072
073 catch (Exception ex)
074
075 {
076
077 Console.WriteLine("Can not update observer. Error:" + ex.Message);
078
079 }
080
081 }
082
083 }
084
085 }
086
087
088
089 public delegate void SeatBeltStatusChangedDelegate(object sender, SeatBeltEventArgs args);
090
091
092
001 public class SeatBeltMonitor
002
003 {
004
005 public void RespondToSeatBeltStatusChange( object sender, SeatBeltEventArgs args)
006
007 {
008
009 if (!args.Locked)
010
011 Console.WriteLine("Please lock your seat belt!");
012
013 else
014
015 Console.WriteLine("Seat belt OK.");
016
017 }
018
019 }
001 static void Main(string[] args)
002
003 {
004
005 SeatBelt seatBelt = new SeatBelt();
006
007 SeatBeltMonitor monitor = new SeatBeltMonitor();
008
009
010
011 seatBelt.StatusChanged += new SeatBeltStatusChangedDelegate(monitor.RespondToSeatBeltStatusChange);
012
013 // Prints OK.
014
015 seatBelt.Locked = true;
016
017
018
019 //Prints warning
020
021 seatBelt.Locked = false;
022
023
024
025 seatBelt.StatusChanged -= new SeatBeltStatusChangedDelegate(monitor.RespondToSeatBeltStatusChange);
026
027 // Nothing will be printed
028
029 seatBelt.Locked = true;
030
031
032
033
034
035 Console.Read();
036
037 }
038
039
040
2150cb82-58bf-44f5-9f4c-f8a8c759b825|1|4.0