I want to discuss a concept that has been revolutionary to my coding style. For now I'm calling it Interfaces Everywhere.
The idea has been stated simply: "Program to an Interface, not to an Implementation."
As for me, I heard that and rolled my eyes. "It's just some fancy theory that very large applications might make use of" right? Wrong :) This thing has hit me like a ton of bricks. Honestly, I feel like I've turned the corner - like this is the difference between being a professional developer and.. well, and I don't know what. Obviously I've been a professional developer for a long time now. I haven't been this energized in years! So where can I begin?
Consider this humble code:
See any problems? At first glance, my old answer would have been "no." I'd expect that some lofty academic type might complain about the hard-coded values (500 and 1000). Or the same for the return values. I'd consider for a moment moving those to a config file or most likely make them database driven. I'd be confident that I had achieved "Separation of Concerns" by having the DAL in place to get my data. No "ADO in the codebehind" for me!
But the problem here is how this class uses the DAL. Look at another way to do this same thing:
I've done a few things:
1) Created an Interface: ISalesTotalProvider. This interface has a very simple definition:
2) Created a class that implements this Interface: DALBasedSalesTotalProvider. This class simply calls the DAL the exact same way the original code did.
3) Added a private instance variable to the original class, but declared it of type ISalesTotalProvider (the interface) rather than a concrete type:
4) Added a default constructor to the original class that sets this variable to an instance of the DALBasedSalesTotalProvider:
5) Used the instance variable to get the data instead of directly calling into the DAL:
Now at this point nothing has changed! The software would run exactly as it always did - except I've now written a lot more code. What's the point of that? Notice I did one other thing though:
6) Added a constructor to the original class that allows the caller to pass in an instance of ISalesTotalProvider:
This makes all the difference in the world...
Now that I can pass in any arbitrary class as long as it implements the Interface, well... I can create a class like this:
Notice this new class could "stand in place" of the "normal" DALBasedSalesTotalProvider. The big difference is that it has a constructor that allows the caller to set an arbitrary amount that will later be returned by the call to GetCurrentSalesTotal().
It is very important to note that the Interface defines that this class will have a method called GetCurrentSalesTotal that must return a Double. The Interface does not define how the class must generate that number.
Why would I want to do this you might ask? I almost hate to answer that question. In truth, there are many reasons, but the main reason is testablity. No, I'm not a TDD advocate. I like my Intellisense too much for that. But let me ask you, Do you have a great amount of test coverage for your code? Or does this scenario ring a bell to you instead:
My test ran fine yesterday - but not today! I searched and searched and the code hasn't changed!! What is wrong? It took me a while to find out - the database has changed! Someone entered a new sale into the system! How am I going to account for that? Am I supposed to keep a separate database just for my test code? What about my tests that modify data? I suppose I could always "rollback..." What a pain! I'm not going to spend much time writing Unit Tests. Maybe later, when I have more time...
Unit Testing is much harder than it has to be when you have hard-coded dependencies like I did in the first sample above. In order to test the "real business logic" in this case we had to first hit the database. What we have done here is removed the dependency on the database so that the test can run without it. See the sample test code next:
The test(s) above do not hit the database at all. Rather they provide different values that would have otherwise been returned from the database, so that the actual method being tested - the business logic - can be tested for each desired outcome. Certainly it is easy now to test "edge cases" such as "what if the database returned 0 or 99999999999?"
I should let you know - this wasn't really my idea :) There is a "name" for this type of thing. As a matter of fact, there are many names for the pieces of this puzzle. Some of the more popular are:
I should also point out that in the example I've provided we have not really removed the dependency from the original class. It still has a default constructor that directly references the DALBasedSalesTotalProvider:
There are two reasons for this:
- We can assume that the original class is called from some existing software. That software has remained unchanged, and it still works exactly like it always has. It instantiates our class with the default constructor like it always has. All of the work we've done has not affected that software at all. Yet, we've greatly increased the testibility of the class.
- It is late and I'm going to go to sleep now :)
Seriously though... I've only touched the surface of this topic. I feel like going beyond this simple example will be a journey of many years. There are so many options, so many opinions as to what is best, so many challenges to overcome. I've already gone quite a bit beyond this simple method in my own work. I'll write about that when I get a chance. Until then, here are some links you might find interesting:
First, there are three very popular frameworks for .NET that do the above in their own way:
The Hollywood Principle - "Don't call us, we'll call you." And how this applies to good software development.
Introducing Dependency Injection Frameworks - A great article with a friendly introduction to StructureMap to boot.
Inversion of Control and Dependency Injection with Castle Windsor Container:
Windsor IoC Container in a Lunch Break
Windsor Container Tutorials
MSDN article on Design Patterns featuring Spring.NET