Browse DevX
Sign up for e-mail newsletters from DevX


The Zen of Inversion of Control : Page 3

Make your classes more useful by minimizing how concretely they specify external dependencies.




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

The Era of Interfaces

As a part of the Rocks-a-Lot Reporting Extension, your team has implemented two major changes:

First, the team changed the language for creating extensions from C# to Visual Basic. The company determined that they have more Visual Basic software development resources available, (plus Vicki Vice President had coded in classic Visual Basic "a long time ago"). This is not a problem; the development principles that I'll show you apply to any language.

Second, the scanner class has shifted from using delegates to using flexible interfaces that support multiple methods. I'll walk you through a brief history of the key interfaces in the order that the team introduced the interface into the program:

The first interface is essentially the delegate predicate function from above packaged as an interface. The GUI form injects the filter interface as a constructor parameter when it instantiated the Scanner class:

Public Sub New(ByVal filter As IFilterFile)

The next interface (IProcessFile), handles processing for files that pass the relevancy filter:

Public Sub New(ByRef filter As IFilterFile, _ ByVal processor As IProcessFile)

This next interface handles the post processing of all files that passed the relevancy filter (IPostProcessFile):

Public Sub New(ByVal filter As IFilterFile,_ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile)

As you can see here, the next interface (IStorageSystem) abstracts the physical file system (introduced to allow for unit testing and to allow the scanner to process XML files that hold summary data):

Public Sub New(ByVal storage As IStorageSystem, _ ByVal filter As IFilterFile, _ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile)

Now it's time to handle the file contents. This next interface splits the processing of a relevant file into two interfaces: one that extracts/builds the contents of the relevant file and a second to process those contents:

Public Sub New(ByVal storage As IStorageSystem, _ ByVal filter As IFilterFile, _ ByVal contentFilter As IFilterContents, _ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile)

Next, here's an interface that formats the contents of the processed data:

Public Sub New(ByVal storage As IStorageSystem, _ ByVal filter As IFilterFile, _ ByVal contentFilter As IFilterContents, _ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile, _ ByVal formatter As IFormatOutput)

Well, you can probably see where this is going. The truth is that after I taught the Visual Basic programmers about interfaces and dependency injection, they went a little (actually, a lot) overboard. If it moved, there was an interface defined and at least one (but usually just one) class that implemented the interface. The addition of "Moderation in All Things" posters in the development workspace came too late to do any real good.

Some developers argue that the use of interfaces can degrade performance (true, but typically not particularly relevant), but the real issue with going overboard on interfaces is one of maintenance. The team got to a point in the development of the Scanner class where they had a constructor with nine different parameters, each representing the implementation of a particular interface needed by the Scanner class. The team's noble Scanning class had become the poster child for pincushions around the world: injection after injection. The biggest problem was that the responsibility for passing the interface implementations to the Scanner class lay with the GUI. When users clicked on the "Capture Rocks-a-Lot Reporting Data" (the actual unedited caption for the button), the GUI would appear and configure each interface implementations in preparation for passing them as parameter values in the Scanner constructor. Remember my earlier description of the "open closed" principle? Suppose you want to "interview" the Scanner class. The interview might go something like this:

"I understand the need to be a team player. If something comes up that needs to be done and I can be of help, then I stand ready to take on the task. But I have to tell you, every time this Scanner class needs a new interface, the developers come trooping through my front room with their muddy boots. I have to have the carpet cleaned each time that happens. Mind you, I don't begrudge Scanner any of the interfaces. Lord knows, Scanner does yeoman duty. Without Scanner we would all be out of jobs, but my question to you is, why do I have to be involved? My GUI does not change, but here I am instantiating and configuring things that I really have no knowledge of. I wake up some mornings with the shakes: what would happen if I mess up? There just has to be a better way!"

You want to be able to finish the work on the GUI for once and for all. In an ideal world, changes to the Scanner class that do not absolutely require changes to the GUI logic should never cause changes to the GUI class. The problem is that you're injecting the dependencies through the Scanner's constructor, and the GUI is responsible for the instantiation of the Scanner class.

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