oday's customers demand easier-to-modify and more flexible applications than ever before. In this article, I will show you how to design applications with extensibility points so that they will grow with the clients' needs as well as provide a way to "swap" functionality in and out as needed.
Throughout my years in software development, many concepts and paradigms have been introduced to improve the process of writing code and defining how software should be developed. For the most part, each has built on its predecessors, enhancing the development process each time. Object-oriented programming redefined how developers think of and communicate with application entities. SOA showed how to expose object-oriented entities so they could service both similar and dissimilar clients. And the design-pattern craze hit the industry with the publishing of the famous GoF book. All together, these things have inspired the creativity of many developers, including me. I'm going to show you how to use your existing OOP and pattern knowledge to develop applications in a way that lets you change and/or enhance them with minimum effort and in a clean, elegant, and efficient manner.
You'll see information and examples of three different patterns that I use to make my applications extensible. None of these patterns is absolutely defined in any patterns manual, though each bears resemblance to one or more patterns in the GoF catalog.
- Providers. This pattern has its roots in the Strategy pattern and it lets you design your data and behavior in an abstraction so that you can swap out implementation at any time (see the sidebar "Providers in the Framework" for more information about how the .NET Framework uses this pattern).
- Plug-Ins. This pattern builds on the same abstraction design I'll use in writing providers and lets you build sections of your site in swappable modules. In a way plug-ins are similar to providers; however, where you generally use providers to obtain information, you typically use plug-ins to perform tasks—though many will argue quite successfully that their definitions can be interchangeable.
- Modules. Modules have their roots in the Chain of Responsibility pattern, and take plug-ins to the next level by allowing you to define many plug-ins within one class, thus centralizing the extensibility capabilities in your application.
To illustrate how the three patterns evolve, I'll start by describing a very trivial three-step process that I'll build on as the article progresses. I'll code this process concretely first, and then start applying my three patterns to it. The three-step process involves obtaining the name of a text file, reading in string data from it, and logging that data to another file.
A Concrete Example
I'll first start by writing a class that will house the functionality I want for my three-step process. I'll call this class FileReader.
Here's the interface for the FileReader class. You can find the complete code in Listing 1
' In VB:
Public Function GetFileName() As String
Public Function GetFileData(
ByVal file As String) As String
Public Sub LogTextData(
ByVal data As String)
// In C#:
public string GetFileName()
public string GetFileData(string file)
public void LogTextData(string data)
This code shows that the GetFileName
method returns the name of a file, which FileReader then sends to the GetFileData
retrieves a string of data, which it sends in turn to the LogTextData
Now, assume you were building an application to run this little three-step process. Integrating this into a form or a controller class of some kind would not be difficult at all, and would be quite well-accepted by most developers in the industry. Unfortunately, if anything ever changes with the way the application obtains a file name or the data, it would involve coming back into this code and changing it appropriately.
A client can use my FileReader class as follows:
' In VB:
Dim o_FileReader As FileReader = New
Dim s_File As String =
Dim s_Data As String = _
If s_Data <> "" Then
// In C#:
FileReader o_FileReader = new
string s_File =
string s_Data =
if (s_Data != "")
To avoid having to change the code later, you can abstract the interface and separate it from the implementation. A client application, be it a class or a form, will then communicate only through the abstraction. In the interest of even greater flexibility, you can generalize this process even further, from this:
Notice the "from" can fit easily into the "to," though not vice-versa.
I came up with this by pretending to be the client, and asking myself exactly what do I need; then coming up with a more generic process that still has the capability of feeding me exactly what I needed. Now all I need to do is turn this into a provider model.
|Editor's Note: This article was first published in the January/February 2008 issue of CoDe Magazine, and is reprinted here by permission.|