Поделиться через


Exploring the Observer Design Pattern

 

Doug Purdy, Microsoft Corporation
Jeffrey Richter, Wintellect

January 2002

Summary: This article discusses the use of design patterns in the Microsoft .NET Framework. An extremely powerful tool for developers or architects in development projects, design patterns ensure that common problems are addressed via well-known and accepted solutions, and that correct code is developed more rapidly, reducing the chance that a mistake will occur in design or implementation. (21 printed pages)

Contents

Introduction
Why Design Patterns?
.NET Framework Patterns
   Observer Pattern
   Event Pattern
Conclusion

Introduction

During the course of a given development project, it is not uncommon to use the concept of design patterns to address certain problems relating to application design and architecture. However, the definition of design patterns is often difficult to convey with any level of accuracy; as such, the concept warrants a brief examination of origin and history.

The origin of software design patterns is attributed to the work of Christopher Alexander. As a building architect, Alexander noted the presence of common problems and related solutions within a given context. A design pattern, as Alexander termed this problem/solution/context triad, enabled an architect to rapidly address issues in a uniform manner during building design. First published twenty-five years ago, A Pattern Language: Towns, Buildings, Construction (Alexander et al, Oxford University Press, 1977) introduced over 250 architectural design patterns and provided the basis for the inclusion of this concept into the realm of software development.

In 1995, the software industry was first widely introduced to the design patterns as they directly related to building applications. The four authors, Gamma, Helm, Johnson, and Vlissides (collectively known as the Gang of Four, or GoF), intersected Alexander's design patterns with the burgeoning object-oriented software movement in their work, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley Pub Co, 1995). Based on their collective experience and examination of existing object frameworks, the GoF provided 23 design patterns that examined common problems and solutions encountered while designing and architecting applications. Following that publication, the concept of design patterns has grown to encompass many problems and solutions encountered in the software domain. In fact, the popularity of design patterns has given rise to the concept of anti-patterns, which are solutions that commonly worsen, rather than solve, the problem at hand.

Why Design Patterns?

Although not a magic bullet (if such a thing exists), design patterns are an extremely powerful tool for a developer or architect actively engaged in any development project. Design patterns ensure that common problems are addressed via well-known and accepted solutions. The fundamental strength of patterns rests with the fact that most problems have likely been encountered and solved by other individuals or development teams. As such, patterns provide a mechanism to share workable solutions between developers and organizations. Regardless of where these patterns find their genesis, patterns leverage this collective knowledge and experience. This ensures that correct code is developed more rapidly and reduces the chance that a mistake will occur in design or implementation. In addition, design patterns offer common semantics between members of an engineering team. As anyone who has been involved in a large-scale development project knows, having a common set of design terms and principles is critical to the successful completion of the project. Best of all, design patterns—if used judicially—can free up your time.

.NET Framework Patterns

Design patterns are not specifically tied to a given language or development platform (although the GoF limited their examples to C++ and Smalltalk); the advent of the Microsoft .NET Framework provides a new opportunity and context to examine design patterns. During the development of the Framework Class Library (FCL), Microsoft applied many of the same patterns first introduced by the GoF in 1994. Due to the breadth of functionality exposed within the .NET Framework, entirely new patterns were developed and introduced as well.

Over the course of this series, we will examine in detail several of the design patterns present within the FCL. The general structure and benefits of each pattern will be considered, followed by an examination of the specific implementation present in the FCL. Although most of the patterns we will examine find their genesis with the GoF, the .NET Framework offers a number of innovative features for which little or no design guidance is presently available. Design patterns related to these new features will be examined as well. Our investigation of design patterns begins with the Observer pattern.

Observer Pattern

One of the overriding principles of object-oriented development is the proper assignment of responsibility in the given application. Each object in the system should focus on a discrete abstraction within the problem domain and nothing more. In short, an object should do one thing and do it well. This approach ensures that a crisp boundary exists between objects, enabling greater reuse and system maintainability.

One area where the proper separation of responsibility is of special importance is the interaction between the user interface and the underlying business logic. During the development of an application, it is quite common for user interface requirements to change rapidly without an associated impact on the rest of the application. In addition, it is also likely that the business requirements will change without regard to the user interface. In many cases, both sets of requirements will change, as anyone with substantial development experience well knows. Without the benefit of separation between the UI and remainder of the application, modification of either portion may adversely impact the whole.

The need to provide a distinct boundary between the user interface and business logic is a common problem that spans applications. As a result, a number of object-oriented frameworks developed since the inception of the GUI have supported the wholesale separation of the user interface from the remainder of the application. Not surprisingly (well maybe a little), most of these adopted a similar design pattern to provide this functionality. This pattern, commonly known as Observer, is advantageous in creating a clear distinction between various objects in the system. In addition, it is not uncommon to find this solution utilized within non-UI related segments of a framework or application as well. As with most other patterns, the usefulness of the Observer pattern extends far beyond its original intent.

Logical Model

Although numerous variations of the Observer pattern exist, the basic premise of the pattern involves two actors, the observer and the subject (those familiar with Smalltalk MVC will know these terms as the view and the model, respectively). Within the context of a user interface, the observer is the object responsible for displaying data to the user. The subject, on the other hand, represents a business abstraction that is modeled from the problem domain. As depicted in Figure 1,****a logical association exists between the observer and subject. When a change occurs in the subject object (e.g. the modification of an instance variable), the observer observes this change and updates its display accordingly.

Ee817669.observerpattern_01(en-us,PandP.10).gif

Figure 1. Observer and Subject Relationship

For example, suppose we have a simple application that tracks stock prices throughout the day. Within this application we have a Stock class that models various stocks that are traded on the NASDAQ. This class contains an instance variable which represents the current ask price, which fluctuates throughout the day. In order to display this information to the user, the application uses a StockDisplay class that writes to stdout (standard output). Within this application, an instance of the Stock class acts as the subject and an instance of the StockDisplay class as the observer. As the ask price changes over the course of the trading day, the current ask price of the Stock instance changes as well (how it changes is not germane). Since the StockDisplay instance is observing the Stock instance, these state changes (modification of the ask price) are displayed to the user as they occur.

The use of this observation process ensures that a boundary exists between the Stock and StockDisplay classes. Suppose that the requirements for the application change tomorrow, requiring the use of a form-based user interface. Enabling this new functionality is a simple matter of constructing a new class, StockForm, to act as an observer. The Stock class would not require any modification whatsoever. In fact, it would not even be aware that such a change was made. Likewise, if a change in requirements dictated that the Stock class retrieved ask price information from another source (perhaps a Web service rather than from a database), the StockDisplay class would not require modification. It simply continues to observe the Stock, oblivious to any changes.

Physical Model

As with most solutions, the devil is in the details. The Observer pattern is no exception. Although the logical model states that the observer observes the subject, this is actually a misnomer when implementing this pattern. More accurately, the observer registers with the subject, expressing its interest in observing. When a state change occurs, the subject notifies the observer of the change. When the observer no longer wishes to observe the subject, the observer unregisters from the subject. These steps are known as observer registration, notification, and unregistration, respectively.

Most frameworks implement registration and notification via callbacks. The UML sequence diagrams shown in Figures 2, 3, and 4 model the objects and method calls typically utilized with this approach. For those unfamiliar with sequence diagrams, the topmost rectangular boxes represent objects while the arrows represent method calls.

Ee817669.observerpattern_02(en-us,PandP.10).gif

Figure 2. Observer Registration

Figure 2 depicts the registration sequence. The observer invokes the Register method on the subject, passing itself as an argument. Once the subject receives this reference, it must store it in order to notify the observer when a state change occurs sometime in the future. Rather than storing the observer reference in an instance variable directly, most observer implementations delegate this responsibility to a separate object, typically a container. Use of a container to store observer instances provides important benefits that we will discuss shortly. With that in mind, the next action in the sequence is the storage of the observer reference denoted by the invocation of the Add method on the container.

Ee817669.observerpattern_03(en-us,PandP.10).gif

Figure 3. Observer Notification

Figure 3 highlights the notification sequence. When a state change occurs (AskPriceChanged), the subject retrieves all the observers within the container by invoking the GetObservers method. The subject then enumerates through the retrieved observers, calling the Notify method, which notifies the observer of the state change.

Ee817669.observerpattern_04(en-us,PandP.10).gif

Figure 4. Observer Unregistration

Figure 4 shows the unregistration sequence. This sequence is performed when the observer no longer needs to observe the subject. The observer calls the UnRegister method, passing itself as an argument. The subject then invokes the Remove method on the container, ending the period of observation.

Returning to our stock application, let's examine the impact of the registration and notification process. During application startup, an instance of the StockDisplay class registers with a Stock instance, passing itself as an argument to the Register method. The Stock instance holds the reference to the StockDisplay instance (in a container). As the ask price attribute changes, the Stock instance notifies the StockDisplay of the change, calling the Notify method. When the application is shutting down, the StockDisplay instance unregisters from the Stock instance, calling the UnRegister method, terminating the relationship between the two instances.

It is worth noting the benefits of utilizing a container, rather than an instance variable to store a reference to the observer. Suppose that our requirements called for a real-time graph of ask prices over the course of the trading day, in addition to the current user interface, StockDisplay. To that end, we create a new class named StockGraph which plots the ask price in the y-axis and the time of day in the x-axis. When the application starts, it registers instances of both the StockDisplay and StockGraph classes with a Stock instance. Since the subject is storing the observers in a container, as opposed to an instance variable, this does not pose an issue. As the ask price changes, the Stock instance notifies both observer instances in its container of the state change. As can be seen, the use of a container provides the flexibility to support multiple observers per subject. This permits a subject to notify a potentially infinite number of observers of state changes, rather than just one.

Although not a requirement, many frameworks provide a set of interfaces for observers and subjects to implement. As shown in the C# and Microsoft® Visual Basic® .NET code samples below,****the IObserver interface exposes one public method, Notify. This interface is implemented by all classes which intend to act as observers. The IObservable interface, implemented by all classes that intend to acts as subjects, exposes two methods, Register and UnRegister. These interfaces generally take the form of abstract virtual classes or true interfaces, if the implementation language supports such constructs. The utilization of these interfaces help to reduce the coupling between the observer and subject. Rather than a tightly coupled association between the observer and subject classes, the IObserver and IObservable interfaces allow operations independent of the implementation. Based on an examination of the interfaces, you will note that all methods are typed to operate on the interface types, as opposed to concrete classes. This approach extends the benefits of the interface programming model to the Observer pattern.

IObserver and IObservable interfaces (C#)

//interface the all observer classes should implement
public interface IObserver {
   
   void Notify(object anObject);
   
}//IObserver

//interface that all observable classes should implement
public interface IObservable {

   void Register(IObserver anObserver);
   void UnRegister(IObserver anObserver);

}//IObservable

IObserver and IObservable interfaces (Visual Basic .NET)

'interface the all observer classes should implement
Public Interface IObserver

    Sub Notify(ByVal anObject As Object)

End Interface

'interface that all observable classes should implement
Public Interface IObservable

    Sub Register(ByVal anObserver As IObserver)
    Sub UnRegister(ByVal anObserver As IObserver)

End Interface

Turning again to our sample application, we know that the Stock class acts as the subject. As such, it would implement the IObservable interface. Likewise, the StockDisplay class, implements the IObserver interface. Since all operations are defined by the interface, rather than the specific class, the Stock class is not bound to StockDisplay class or vice versa. This permits us to rapidly change the specific observer or subject implementation without impacting the rest of the application (replacing the StockDisplay with a different observer or adding an additional observer instance).

In addition to these interfaces, it is not uncommon for a framework to provide a common base class for subjects to extend. The extension of this base class reduces the effort required to support the Observer pattern. The base class implements the IObservable interface, providing the required infrastructure to support the storage and notification of observer instances. The C# and Visual Basic .NET code samples below outline such a base class named ObservableImpl. This class****delegates observer storage to a Hashtable instance in the Register and UnRegister methods, although potentially any container will suffice (our example uses a Hashtable as the container for convenience, it only takes one method call to unregister a specific observer instance). Also, note the addition of the NotifyObservers method. This method is used to notify the observers stored within the Hashtable. When this method is invoked, the container is enumerated, calling the Notify method on the observer instances.

ObservableImpl Class (C#)

//helper class that implements observable interface
public class ObservableImpl:IObservable {
      
   //container to store the observer instance (is not synchronized for 
         this example)
   protected Hashtable _observerContainer=new Hashtable();
   
   //add the observer
   public void Register(IObserver anObserver){
      _observerContainer.Add(anObserver,anObserver); 
   }//Register
      
   //remove the observer
   public void UnRegister(IObserver anObserver){
      _observerContainer.Remove(anObserver); 
   }//UnRegister

   //common method to notify all the observers
   public void NotifyObservers(object anObject) { 
         
      //enumeration the observers and invoke their notify method
      foreach(IObserver anObserver in _observerContainer.Keys) { 

         anObserver.Notify(anObject); 

      }//foreach
      
   }//NotifyObservers

}//ObservableImpl

ObservableImpl Class (Visual Basic .NET)

'helper class that implements observable interface
Public Class ObservableImpl

    Implements IObservable

    'container to store the observer instance (is not synchronized for this 
         example)
    Dim _observerContainer As Hashtable = New Hashtable()

    'add the observer
    Public Sub Register(ByVal anObserver As IObserver) Implements 
         IObservable.Register
        _observerContainer.Add(anObserver, anObserver)
    End Sub

    'remove the observer
    Public Sub UnRegister(ByVal anObserver As IObserver) Implements 
         IObservable.UnRegister
        _observerContainer.Remove(anObserver)
    End Sub

    'common method to notify all the observers
    Public Sub NotifyObservers(ByVal anObject As Object)

        Dim anObserver As IObserver

        'enumerate the observers and invoke their notify method
        For Each anObserver In _observerContainer.Keys

            anObserver.Notify(anObject)

        Next

    End Sub

End Class

Our sample application can benefit from this base class infrastructure by modifying the Stock class to extend the ObservableImpl class, rather than providing its own specific implementation of the IObservable interface. Since the ObservableImpl class implements the IObservable interface, no change is required on the StockDisplay class. This approach really simplifies the implementation of the Observer pattern, allowing multiple subjects to reuse the same functionality while maintaining a loosely coupled relationship between the involved classes.

The Observer examples in C# and Visual Basic. NET below highlight the utilization of the IObservable and IObserver interfaces, as well as the ObservableBase class in the context of our stock application. In addition to the Stock and StockDisplay classes, this example uses MainClass to associate the observer and subject instances and modify the AskPrice property of the Stock instance. This property is responsible for invoking the base class's NotifyObservers method, which in turn notifies the instance of the associated state change.

Observer Example (C#)

//represents a stock in an application
public class Stock:ObservableImpl {
      
   //instance variable for ask price
   object _askPrice;

   //property for ask price
   public object AskPrice {
 
      set {    _askPrice=value;
         base.NotifyObservers(_askPrice);
                   }//set
      
   }//AskPrice property
 
}//Stock

//represents the user interface in the application
public class StockDisplay:IObserver {

   public void Notify(object anObject){ 
      Console.WriteLine("The new ask price is:" + anObject); 
   }//Notify

}//StockDisplay

public class MainClass{

   public static void Main() {

      //create new display and stock instances
      StockDisplay stockDisplay=new StockDisplay();
      Stock stock=new Stock();

      //register the grid
      stock.Register(stockDisplay);

      //loop 100 times and modify the ask price
      for(int looper=0;looper < 100;looper++) {
         stock.AskPrice=looper;
      }

      //unregister the display
      stock.UnRegister(stockDisplay);
      
   }//Main
   
}//MainClass

Observer Example (Visual Basic .NET)

'Represents a stock in an application
Public Class Stock

    Inherits ObservableImpl

    'instance variable for ask price
    Dim _askPrice As Object

    'property for ask price
    Public WriteOnly Property AskPrice()

        Set(ByVal value As Object)
            _askPrice = value
            NotifyObservers(_askPrice)
        End Set

    End Property

End Class

'represents the user interface in the application
Public Class StockDisplay

    Implements IObserver

    Public Sub Notify(ByVal anObject As Object) Implements IObserver.Notify

        Console.WriteLine("The new ask price is:" & anObject)

    End Sub

End Class

Public Class MainClass

    Shared Sub Main()

   'create new grid and stock instances
       Dim stockDisplay As StockDisplay = New StockDisplay()
   Dim stock As Stock = New Stock()
   
   Dim looper As Integer

        'register the display
        stock.Register(stockDisplay)

        'loop 100 times and modify the ask price
        For looper = 0 To 100
            stock.AskPrice = looper
        Next looper

        'unregister the display
        stock.UnRegister(stockDisplay)

End Sub

Observer Pattern in the .NET Framework

Based on our understanding of the Observer pattern, let us now turn our attention to the use of this pattern within the .NET Framework. Those of you with passing familiarity of the types exposed in the FCL will note that no IObserver, IObservable, or ObservableImpl types****are present in the Framework. The primary reason for their absence is the fact that the CLR makes them obsolete after a fashion. Although you can certainly use these constructs in a .NET application, the introduction of delegates and events provides a new and powerful means of implementing the Observer pattern without developing specific types dedicated to support this pattern. In fact, as delegates and events are first class members of the CLR, the foundation of this pattern is incorporated into the very core of the .NET Framework. As such, the FCL makes extensive use of the Observer pattern throughout its structure.

Much has been written concerning the inner workings of delegates and events, thus there is no need for a similar accounting here. It suffices to simply state that a delegate is the object-oriented (and type-safe) equivalent of a function pointer. A delegate instance holds a reference to an instance or class method, permitting anonymous invocation of the bound method. Events are special constructs declared on a class that help to expose state changes to interested objects at run time. An event represents a formal abstraction (supported by the CLR and various compilers) of the registration, unregistration, and notification methods we used previously to implement the Observer pattern. Delegates are registered with specific events at run time. When the event is raised, all registered delegates are invoked so that they receive notification of the event. For a more in-depth examination of delegates and events, see An Introduction to Delegates.

Prior to examining delegates and events in the context of the Observer pattern, it is worth noting that the various languages supported by the CLR are free to expose the delegate and event mechanism as the language designer sees fit. As such, a comprehensive study of these features across languages is not possible. For the purposes of the following discussion, we will focus on the C# and Visual Basic .NET implementation of these features. If you are using a language other than C# or Visual Basic .NET, please refer to the documentation for more information about how delegates and events are supported in your language.

Using the terms defined by the Observer pattern, the class declaring the event is the subject. Unlike our previous use of the IObservable interface and ObservableImpl class, the subject class is not required to implement a given interface or extend a base class. The subject merely needs to expose an event; nothing more is required. The creation of the observer is slightly more involved, yet infinitely more flexible (which we will discuss later). Rather than implementing the IObserver interface and registering itself with the subject, an observer must create a specific delegate instance and register this delegate with the subject's event. An observer must utilize a delegate instance of the type specified by the event declaration, otherwise registration will fail. During the creation of this delegate instance, the observer passes the name of the method (instance or static) that will be notified by the subject to the delegate. Once the delegate is bound to the method, it may be registered with the subject's event. Likewise, this delegate may be unregistered from the event, as well. Subjects provide notification to observers by invocation of the event.

If you are unfamiliar with delegates and events this may seem like a lot of work to implemented the Observer pattern, especially compared to our previous use of the IObserver and IObservable interfaces. It is, however, a lot simpler than it sounds and far easier to implement. The C# and Visual Basic .NET code samples below highlight the required class modifications within our sample application to support delegates and events. Note the lack of any base classes or interfaces used by the Stock or StockDisplay classes to the support the pattern.

Observer using delegates and events (C#)

public class Stock {

   //declare a delegate for the event
   public delegate void AskPriceDelegate(object aPrice);
   //declare the event using the delegate
   public event AskPriceDelegate AskPriceChanged;

   //instance variable for ask price
   object _askPrice;

   //property for ask price
   public object AskPrice {
 
      set { 
         //set the instance variable
         _askPrice=value; 

         //fire the event
         AskPriceChanged(_askPrice); 
      }
      
   }//AskPrice property
 
}//Stock class

//represents the user interface in the application
public class StockDisplay {

   public void AskPriceChanged(object aPrice) {
      Console.Write("The new ask price is:" + aPrice + "\r\n"); }

}//StockDispslay class

public class MainClass {

   public static void Main(){

      //create new display and stock instances
      StockDisplay stockDisplay=new StockDisplay();
      Stock stock=new Stock();
   
      //create a new delegate instance and bind it
      //to the observer's askpricechanged method
      Stock.AskPriceDelegate aDelegate=new
         Stock.AskPriceDelegate(stockDisplay.AskPriceChanged);
         
      //add the delegate to the event
      stock.AskPriceChanged+=aDelegate;

      //loop 100 times and modify the ask price
      for(int looper=0;looper < 100;looper++) {
         stock.AskPrice=looper;
      }

      //remove the delegate from the event
      stock.AskPriceChanged-=aDelegate;

   }//Main

}//MainClass

Observer using delegates and events (Visual Basic .NET)

'represents a stock in an application
Public Class Stock

   'declare a delegate for the event 
   Delegate Sub AskPriceDelegate(ByVal aPrice As Object)
   
   'declare the event using the delegate
    Public Event AskPriceChanged As AskPriceDelegate

    'instance variable for ask price
    Dim _askPrice As Object

    'property for ask price
    Public WriteOnly Property AskPrice()

        Set(ByVal value As Object)
            _askPrice = value
            RaiseEvent AskPriceChanged(_askPrice)
        End Set

    End Property

End Class

'represents the user interface in the application
Public Class StockDisplay

    Public Sub Notify(ByVal anObject As Object)

        Console.WriteLine("The new ask price is:" & anObject)

    End Sub

End Class

Public Class MainClass

    Shared Sub Main()

   'create new display and stock instances
       Dim stockDisplay As StockDisplay = New StockDisplay()
   Dim stock As Stock = New Stock()

        Dim looper As Integer

        'register the delegate
        AddHandler stock.AskPriceChanged, AddressOf stockDisplay.Notify

        'loop 100 times and modify the ask price
        For looper = 0 To 100
            _stock.AskPrice = looper
        Next looper

        'unregister the delegate
        RemoveHandler stock.AskPriceChanged, AddressOf stockDisplay.Notify

    End Sub

Once you are familiar with delegates and events, their inherent strengths become evident. Unlike the IObserver and IObservable interfaces and ObservableImpl class, the use of delegates and events greatly simplifies the amount of work that must be undertaken to implement this pattern. The CLR and complier provide the basis for observer container management, as well as a common calling convention for registering, unregistering, and notifying observers. Perhaps the greatest benefit of delegates is their intrinsic ability to refer to any method whatsoever (provided it conforms to the same signature). This permits any class to act as an observer, independently of what interfaces it implements or classes it specializes. Although the use of the IObserver and IObservable interfaces work to reduce the coupling between the observer and subject classes, use of delegates completely eliminates it.

Event Pattern

Based on events and delegates, the FCL uses the Observer pattern quite extensively. The designers of the FCL fully realized the inherent power of this pattern, applying it to both user interface and non-UI specific features throughout the Framework. This use, however, is a slight variation on the base Observer pattern, which the Framework team has termed the Event Pattern. In general, this pattern is expressed as formal naming conventions for delegates, events, and related methods involved in the event notification process. Microsoft recommends that all applications and frameworks that utilize events and delegates adopt this pattern, although there is no enforcement in the CLR or standard compiler (beware the pattern police!).

The first, and perhaps most important, of these conventions is the name of the event exposed by the subject. This name should be self-evident as to the state change that it represents. Bear in mind that this convention, as with all other such conventions, is subjective in nature. The intent is to provide clarity to those utilizing your event. The remaining items of the event pattern leverage the proper naming of the event, thus the importance of this step to the pattern.

Returning to our trusty example, let us examine the impact of this convention on the Stock class. A suitable means of deriving an event name is to utilize the name of the field that was modified in the subject class as the root. Since the name of the field that is modified in the Stock class is the _askPrice, a reasonable event name would be AskPriceChanged. It is fairly evident that the name of this event is far more descriptive than say, StateChangedInStockClass. Thus, the AskPriceChanged event name adheres to the first convention.

The second convention in the Event pattern is properly naming of the delegate and its signature. The delegate name should be composed of the event name (selected via the first convention) with the word Handler appended. The pattern calls for the delegate to specify two parameters, the first supplying a reference to the sender of the event and the second providing contextual information to the observer. The name of the first parameter is simply sender. This parameter must be typed as System.Object. This is due to the fact the delegate could be bound to potentially any method on any class in the system. The name of the second parameter, even simpler than the first, is e. This parameter is typed as System.EventArgs or some derived class (more on this in a moment). Although the return type of the delegate depends on your implementation needs, most delegates that implement this pattern do not return any value at all.

The second parameter of the delegate, e, requires slightly more attention. This parameter enables the subject object to pass arbitrary contextual information to the observer. If such information is not required, an instance of System.EventArgs will suffice, as instances of this class represent the absence of contextual data. Otherwise, a class derived from System.EventArgs should be constructed with the appropriate implementation to provide this data. This class must be named after the event name with the word EventArgs appended.

Referring to our Stock class, this convention requires that the delegate which handles the AskPriceChanged event to be named, AskPriceChangedHandler. In addition, the name of the second parameter of this delegate should be named AskPriceChangedEventArgs. Since we need to pass the new ask price to the observer, we need to extend the System.EventArgs class, naming this class AskPriceChangedEventArgs, and providing the implementation to support passing this data.

The last item in the Event pattern is the name and accessibility of the method on the subject class that is responsible for firing the event. The name of this method should be composed of the event name with the addition of an On prefix. The accessibility of this method should be set to protected. This convention applies only to non-sealed (NotInheritable in VB) classes, as it serves as a well-known invocation point for derived classes to invoke observers registered with the base class.

Applying this last convention to the Stock class completes the Event pattern. Since the Stock class is not sealed, we must add a method to fire the event. In accordance with the pattern, the name of this method is OnAskPriceChanged. The C# and Visual Basic .NET code samples below****show a complete view of the Event pattern applied to the Stock class. Note the use of our specialization of the System.EventArgs class.

Event Pattern Example (C#)

public class Stock {

      //declare a delegate for the event
      public delegate void AskPriceChangedHandler(object sender, 
            AskPriceChangedEventArgs e);
      //declare the event using the delegate
public event AskPriceChangedHandler AskPriceChanged;

      //instance variable for ask price
      object _askPrice;

      //property for ask price
      public object AskPrice {

         set { 
            //set the instance variable
_askPrice=value; 

//fire the event
OnAskPriceChanged(); 
     }
      
      }//AskPrice property
               
               //method to fire event delegate with proper name
      protected void OnAskPriceChanged() {

         AskPriceChanged(this,new AskPriceChangedEventArgs(_askPrice));

      }//AskPriceChanged

   }//Stock class

   //specialized event class for the askpricechanged event
   public class AskPriceChangedEventArgs:EventArgs {

      //instance variable to store the ask price
      private object _askPrice;

      //constructor that sets askprice
      public AskPriceChangedEventArgs(object askPrice) { _askPrice=askPrice; }

      //public property for the ask price
      public object AskPrice { get { return _askPrice; } }

   }//AskPriceChangedEventArgs

Event Pattern Example (Visual Basic .NET)

Public Class Stock

    'declare a delegate for the event
    Delegate Sub AskPriceChangedHandler(ByVal sender As Object, ByVal e As 
            AskPriceChangedEventArgs)

    'declare the event using the delegate
    Public Event AskPriceChanged As AskPriceChangedHandler

    'instance variable for ask price
    Dim _askPrice As Object

    Public Property AskPrice() As Object

        Get
            AskPrice = _askPrice
        End Get

        Set(ByVal Value As Object)

            _askPrice = Value

            OnAskPriceChanged()

        End Set

    End Property

    'method to fire event delegate with proper name
    Protected Sub OnAskPriceChanged()

        RaiseEvent AskPriceChanged(Me, New AskPriceChangedEventArgs(_askPrice))

    End Sub


End Class

Public Class AskPriceChangedEventArgs

    Inherits EventArgs

    'instance variable to store the ask price
    Dim _askPrice As Object

    Sub New(ByVal askPrice As Object)
        _askPrice = askPrice
    End Sub

    Public ReadOnly Property AskPrice() As Object
        Get
            AskPrice = _askPrice
        End Get
    End Property

End Class

Conclusion

Based on this examination of the Observer pattern, it should be evident that this pattern provides an ideal mechanism to ensure crisp boundaries between objects in an application, regardless of their function (UI or otherwise). Although fairly simple to implement via callbacks (using the IObserver and IObservable interfaces), the CLR concepts of delegates and events handle the majority of the "heavy lifting," as well as decreasing the level of coupling between subject and observer. Proper use of this pattern really goes a long way to ensure that applications are able to evolve. As your UI and business requirements change over time, the Observer pattern will ensure that your job is not as hard as it could be.

Used effectively, design patterns are a powerful tool in the development of flexible applications. This article was written to demonstrate the soundness of the patterns approach, as well as to highlight one of the patterns used in the .NET Framework. A future article will further explore the patterns in the FCL, as well as touch on some patterns for building effective Web services. Until then…

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

© Microsoft Corporation. All rights reserved.