Design for Extensibility Today’s clients seem to be getting more and more demanding regarding the flexibility of their applications and the speed in which modifications can be made. 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, there have been many concepts and paradigms introduced in writing code as well as many methodologies for defining how software should be developed. For the most part, each has built on the previous, enhancing the development process each time. Object-oriented programming redefined how we think of entities within an application and communicate with them. SOA showed us how to expose object-oriented entities in a way that they can service similar and dissimilar clients. Several years ago, the design-pattern craze hit the industry with the publishing of the famous GoF book (see sidebar “Additional References”). All these things put together 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 they can be changed and/or enhanced with minimum effort and in a clean, elegant, and efficient manner. Extensibility Patterns Throughout the course of this article, I’m going talk about three different patterns that I use to make my applications extensible. None of these 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. Plug-Ins This 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 they are similar to providers but where you generally use providers to obtain information, you use plug-ins typically 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 your extensibility capability in your application. To better illustrate how the three patterns I’m going to show you evolve, I’ll start by describing a very trivial 3-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 3-step process will involve obtaining the name of a text file, reading in string data from it, and logging that data to another file. Doing it Concretely I’ll first start by writing a class that will house the functionality I want for my 3-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)
From what this code shows, the GetFileName method is returning the name of a file, which FileReader then sends into the GetFileData method to retrieve a string of data, which it then sends into the LogTextData method. Now, say you were building an application that was going to run this little 3-step process. Integrating this into a form or a controller class of some kind would not be difficult at all and 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 like so: In VB: Dim o_FileReader As FileReader = New FileReader()
Dim s_File As String = o_FileReader.GetFileName()
Dim s_Data As String = _ o_FileReader.GetFileData(s_File)
If s_Data <> "" Then o_FileReader.LogTextData(s_Data) End If
In C#: FileReader o_FileReader = new FileReader();
string s_File = o_FileReader.GetFileName();
string s_Data = o_FileReader.GetFileData(s_File);
if (s_Data != "") { o_FileReader.LogTextData(s_Data); }
So what I’m going to do is 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 I’m about to design. In the interest of even greater flexibility, I’m going to generalize this process even further. I’m going to go from: - GetFileName
- GetFileData
- LogTextData
to: - GetSource
- GetData
- LogData
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. |