Browse DevX
Sign up for e-mail newsletters from DevX


Design for Extensibility : Page 5

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




Building the Right Environment to Support AI, Machine Learning and Deep Learning

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