dcsimg
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Design for Extensibility : Page 5

Build extensibility and flexibility into your applications to simplify maintenance and accommodate changes.


advertisement
Extensibility Modules
Extensibility modules let you consolidate your plug-ins into a centralized class, giving all your developers one place to go to figure out what kind of extensibility is available to them for a given application.

If you're familiar with the concept of HttpModules, you're going to feel right at home with this subject.

The preceding section showed you how to write an interface for each type of plug-in; the signature of the interface member(s) identifies the data that the plug-in class needs for its processing. Extensibility modules require a little more work, but it's a lot more organized. The example used here shows you how to do this by writing what would otherwise be three different plug-ins. One serves the same purpose as the one in the plug-ins example, a "post log" plug-in, called "post processing." Another will be a "pre-processing" plug-in, and a third is a "check data source" plug-in.

The points of extensibility I'll insert into my client will be as follows:

  • The "Check Data Source" plug-in process will run immediately following the GetSource method in my provider, and will determine whether processing should continue.
  • The "Pre-Process" plug-in process will run just before you "log" the data.
  • The "Post-Process" plug-in process will run immediately after you "log" the data.
The cool thing here is that I'm going to do this using just one interface. But because of that, I'm going to need an EventArgs-based class for each of my three plug-in scenarios. Like any conventional EventArgs-based class, each of class will contain the data I need to get from the client app into the "plug-in" process code and back.

   ' In VB:
   Public Class CheckDataSourceEventArgs
      Inherits CancelEventArgs
      Public Sub New(ByVal source As String)
         _Source = source
      End Sub
   
      Protected _Source As String
      Public Property Source() As String
         Get
            Return _Source
         End Get
         Set(ByVal value As String)
            _Source = value
         End Set
      End Property
   End Class
   
   Public Class PreProcessDataEventArgs
      Inherits EventArgs
      Public Sub New(ByVal data As String)
         _Data = data
      End Sub
   
      Protected _Data As String
      Public Property Data() As String
         Get
            Return _Data
         End Get
         Set(ByVal value As String)
            _Data = value
         End Set
      End Property
   End Class
   
   Public Class PostProcessDataEventArgs
      Inherits PreProcessDataEventArgs
      Public Sub New(ByVal data As String)
         MyBase.New(data)
      End Sub
   End Class
   
   
   // In C#:
   public class CheckDataSourceEventArgs :
      CancelEventArgs
   {
      public CheckDataSourceEventArgs(
         string source)
      {
         _Source = source;
      }
      protected string _Source;
      public string Source
      {
         get
         {
            return _Source;
         }
         set
         {
            _Source = value;
         }
      }
   }
   
   public class PreProcessDataEventArgs : EventArgs
   {
      public PreProcessDataEventArgs(string data)
      {
         _Data = data;
      }
      protected string _Data;
      public string Data
      {
         get
         {
            return _Data;
         }
         set
         {
            _Data = value;
         }
      }
   }
   
   public class PostProcessDataEventArgs : 
      PreProcessDataEventArgs
   {
      public PostProcessDataEventArgs(
         string data) : base(data)
      {
      }
   }
As you can see, the PostProcessDataEventArgs class is going to need the same data as the PreProcessDataEventArgs class. To make things simpler, I'm just inheriting one from the other.

With the EventArgs class to carry information to and from each plug-in process in place, here's a class that will use them, called ModuleEvents, which contains a set of properties—one for each plug-in process I want to define. Each property type is a delegate that defines a signature of the corresponding EventArgs-based class. Each property wraps a member variable of the same delegate type. Does that sound confusing enough? Take a look at the code and it should clear things up.

First, you need the delegate types, but fortunately, you need only one, thanks to the wonderful world of .NET Generics:

   ' In VB:
   Public Delegate Sub 
      AcmeModuleDelegate(Of T) _
      (ByVal e As T)
   // In C#:
   public delegate void 
   AcmeModuleDelegate<T>(T e);
Now the property types can all use the same delegate type, but each can provide a different value for the generic type. To create the member variables:

   ' In VB:
   Private _CheckDataSource As _
      AcmeModuleDelegate( _
      Of CheckDataSourceEventArgs)
   Private _PreProcessData As _
      AcmeModuleDelegate( _
      Of PreProcessDataEventArgs)
   Private _PostProcessData As _
      AcmeModuleDelegate( _
      Of PostProcessDataEventArgs)
   
   
   // In C#:
   private 
      AcmeModuleDelegate
      <CheckDataSourceEventArgs>   
      _CheckDataSource;
   private 
      AcmeModuleDelegate
      <PreProcessDataEventArgs> 
      _PreProcessData;
   private 
      AcmeModuleDelegate
      <PostProcessDataEventArgs> 
      _PostProcessData;
Now you need to define the public properties that expose the member variables. You can see that in Listing 4.

Now, an instance of ModuleEvents will contain properties that are delegates. As you know, a delegate can have one or more function pointers in its invocation list.

You still need the interface that you'll use to write plug-ins later. As I said earlier, instead of creating a different interface for each plug-in, I'm going to have only one, which looks like this:

   ' In VB:
   Public Interface IAcmeModule
   Sub Initialize(ByVal events As 
      ModuleEvents)
   End Interface
   
   
   // In C#:
   public interface IAcmeModule
   {
   void Initialize(ModuleEvents events);
   }
Notice that there's only one method, Initialize, which receives a ModuleEvents argument type, which I just created. Now, the best way to continue is to write a plug-in using this new model.

From what you've seen so far, developers have only one interface they need to know about to extend this application. So I'm going to write a new class in a new project called NotificationModule. This class will perform the same process as the EmailProcessing plug-in described earlier; it will implement the IAcmeModule interface and implement the Initialize method. The Initialize method, accesses the events argument, whose properties are delegates. The IntelliSense itself will tell me what my possible extensibility points are. Because each one is a delegate, I can just wire a method in this class to that delegate, effectively adding to its invocation list.

   ' In VB:
   Public Class NotificationModule
      Implements IAcmeModule
   
      Public Sub Initialize(ByVal events As _
         Core.ModuleEvents) Implements _
         Core.IAcmeModule.Initialize
         events.PostProcessData = _
            New AcmeModuleDelegate( _
            Of PostProcessDataEventArgs)( _
            AddressOf events_PostProcessData)
      End Sub
      Private Sub events_PostProcessData( _
         ByVal e As PostProcessDataEventArgs)
         ' perform e-mail functionality 
         ' with processed data
      End Sub
   End Class
   
   
   // In C#:
   public class NotificationModule : 
      IAcmeModule
   {
      void IAcmeModule.Initialize(
         ModuleEvents events)
      {
         events.PostProcessData += 
            events_PostProcessData;
      }
      void events_PostProcessData(
         PostProcessDataEventArgs e)
      {
         // perform e-mailing of processed data
      }
   }
As you can see, you can decide within the module class itself which extensibility point you want to tap into.



Thanks for your registration, follow us on our social networks to keep up-to-date