From time to time, I run into developers who ask the question, “what is the point of using an interface?” This is usually followed by a statement such as…
“I just new up a class whenever I need one, and don’t really see the point of using an interface.”
When I hear this, the first thing I do is to ask to see some of their unit tests.
Typically, this is met with a few moments of awkward silence, and then there is a declaration similar to… “Well, I don’t write unit tests. I don’t have time for them, and I really don’t see the point of them.” And my favorite… “Where’s the value add?”
Perhaps you have worked with developers like this.
I have sometimes observed that an associated trait of this approach to programming comes along with a statement or two on how “fast” they write their code.
Now, sometimes fast is good, but rarely at the expense of code that is tightly-coupled, untestable, or not object oriented. The reason for this is that according to Agile Development (2007), more time (as much as 80-90 percent of the life of the production code) is spent maintaining your code compared to the time spent initially writing it. So take the time to write it correctly using proven methods.
According to Osherove (2015) , tightly-coupled and untestable code will cost you and the team greatly in the long run in terms of bug fixes, changing existing features, or in terms of adding new features. Left to continue, some systems erode to the point where they just stop working and the only alternative is a complete re-write.
In any case, let’s suppose that our developer’s name is Bart. Everything goes along okay for a while, but then one day, inevitably, Bart is either out sick, or Bart decides to take some vacation.
Of course a bug is found, and the development manager asks you to take a look at Bart’s code, and please come up with a fix.
So you fire up your debugger and start walking though the code, and oh my, what do you find? — an assortment of monolithic classes and long methods, that in-turn, call other monolithic classes and long methods.
Since there are no interfaces in play, there are dependencies strewn about directly accessing things like databases, web services, the file system, EventLog, or any number of external dependencies. Logging is taking place, but each class creates an instance of the logging class as needed.
You understand that the logging code is a cross-cutting concern. However, you see that the same logging code repeats over and over again, and is scattered throughout many methods and classes. This adds additional noise to the code making it that much more unreadable.
Mr. Groves, in his book AOP in .NET (2015) discusses the advantages of Aspect Oriented Programming, (AOP — which relies on interfaces). Bart is not familiar with these techniques as he is not a fan of interfaces. However, an Aspect is precisely where the cross-cutting concerns like logging, security, authentication, and common things like defensive coding belong. (We will see how to create an Aspect to get rid of the repeating code in another post.)
In any case, perhaps you would like to write some tests for this code, but because there are no interfaces, there are no seams to pass in the required dependencies.
So you are stuck.
Most likely, you will run a console application, set break points, and try to find out where the bug lives. And when you think you’ve found a bug and want to make changes to the code, you’re left with a sinking feeling that perhaps when you check in your changes, you just might break something else.
Because Bart wrote no tests providing a safety net, at best, this approach is now a crap shoot.
Does this scenario sound familiar?
If so, you might be wondering, what does this have to do with interfaces?
The answer is: everything!
It is the use of an interface that allows us to begin to write decoupled code.
Each interface can serve as a seam where we can pass in a dependency of our own choice, real or faked, instead of relying on some hard coded reference to an external resource or other dependency such as the logging class dependency.
The power of the interface lies in the fact that an interface is nothing more than a required behavior. The interface is often called a contract, but I like to use the word behavior as the true focus and intent of an interface.
By stating the ‘behavioral intent’ through an interface, we “decouple” any concrete class’s implementation of the interface. Put another way, we are saying that any class that implements our interface must exhibit certain behaviors.
How the class implements the behavior, we no longer care. As long as the behavioral contract is implemented and followed, we are good to go!
Before we take a look at using an interface, let’s take a look at a class that does not use an interface, and hence provides us no seam.
This class uses an external dependency and its tight coupling prevents us from writing true unit tests. At best, we are forced to write an integration test that must call an external dependency.
Now, is this bad? I would argue three times: yes, yes, yes!
It is essential that we are able to test the behavior of our code without depending on external entities or services. These types of tests are called unit tests by definition. (If you follow Test Driven Development, you naturally will have testable and decoupled code, but this is the subject of another post.)
Integration tests that rely on external entities and other systems certainly have their place, but they do not replace the need for solid unit tests that allow us to “refactor with impunity.”
Let’s look at our Service
class without an interface.
public class Service
{
public string GetUserFirstName(string logonId)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://myEndoint.com/");
var result = request.GetResponse(logonId);
return result.ToString();
}
}
We have no way to pass in our own dependency, because the Service
class has taken on the hard coded responsibility of creating an HTTP request method. So even with this simple class and its one simple method, we are forever tied to an HTTP external dependency.
But what if the endpoint is down and we want to test our code, or we’d like to make and test some related business logic changes? We are pretty much stuck.
Let’s introduce a simple interface to see how our dependency situation changes.
The behavior that we want to enforce and test is in the GetUserFirstName
method. So, let’s put that behind a simple interface and create our first seam.
public interface IService
{
string GetUserFirstName(string LogonId);
}
Now any class that implements our interface must provide an implementation of this method. Let’s do two things to leverage the interface’s behavior specification.
First, we will modify our existing Service
class to require an IService
instance as part of its constructor.
Secondly, we will have the Service
class implement the IService
interface.
In the first case, requiring an instance of the interface in the constructor is a typical dependency inversion technique.
We have moved the responsibility for creating the dependency from the Service
class, and pushed it (or inverted it) back to the caller — hence the name Dependency Inversion.
The caller now must supply an instance of IService
, and this is exactly what we want in our decoupled code!
In the second modification, implementing the IService
interface, we want to guarantee that the GetUserFirstName
method appears on the Service
class.
Let’s take a look at the resulting Service
class code to see what we have done.
public class Service: IService
{
IService service;
public Service(IService service)
{
this.service = service;
}
public string GetUserFirstName(string LogonId)
{
return service.GetUserFirstName(LogonId);
}
}
Notice that our interface is helping to shape our code!
By implementing IService
, we have to provide the GetUserFirstName
method.
We also leveraged the Service
class’s constructor to require an instance of IService
. Using the passed in instance of IService
, we can now delegate the work of GetUserFirstName
to whatever instance of IService
we choose to pass in.
How do we know that we can successfully delegate our return call to service.GetUserFirstName(LogonId)
?
We can be sure because we know any instance of IService
must implement the GetUserFirstName
method. Further, we required and set the local service variable from the Service
class constructor to be of the IService
type.
In our case, any class that implements the IService
interface “IS” an IService
and can be treated as such.
It is important to note that the interface behavior is at work for us by definition, both in the constructor and in the Service
class method implementation.
By taking these simple steps, we have added polymorphic behavior to our code — and this is one of the pillars of object oriented programming.
Polymorphic means “many forms.” But in terms of our IService
example, it simply means that the caller is not aware of the specific implementation details, and it does not need to be aware — it has merely to call the GetUserFirstName
method and the work is handled by our IService
instance.
Now the IService
instance could be a web service class (we will have a separate class for each implementation), File System, SharePoint System, or it could be a test class implementation of IService
; or any other implementation that may come along based on future requirements.
This constructor inversion technique is the basis for implementing the Strategy Pattern, where we “encapsulate what varies.” Many other design patterns in SOLID rely on Dependency Inversion to supply specific concrete instances of interfaces, based on the client’s needs.
The use of an IoC (Inversion of Control) container is often used to pre-wire and register these dependencies, relating each specific interface to a specific concrete class implementation. This is done in the root of the application at startup, known as the composition root. (IoC is the subject of another post.)
In any case, we have created our first seam, and it is this concept that enables decoupled and testable object oriented code.
Now, a unit test might easily be written that looks like this…
[Test]
[Category("Unit")]
public void GetFirstName_UsingNamedTestService_ReturnsFirstNameFromTestService()
{
var factory = new Factory();
IService instance = factory.FactoryMethod("testService");
var result = instance.GetUserFirstName("101");
Assert.AreEqual("FirstNameFromTestService", result);
}
In my next post, I’ll demonstrate using structure map to create the factory method shown above. Without using a switch, the factory will return a specific instance of IService
, solely based on the client passing in a simple string
(the named instance.)
This named instance can be seen on line 6 in the unit test above, and is the string
“testService
”.
This is the essence of the strategy pattern and is used throughout codebases by developers who adhere to the principles of SOLID programming.
All of this object oriented goodness is made possible thanks to the use of the Interface.
References
- Agile Development and Software Maintenance Costs. (2007, March 01). Retrieved December 29, 2017, from http://tynerblain.com/blog/2007/02/28/agile-development-roi-2/
- Groves, M. D. (2013). AOP in .NET: practical aspect-oriented programming. Shelter Island, NY: Manning Publications Co.
- Osherove, R., Martin, R. C., & Feathers, M. (2015). Art of Unit Testing. Shelter Island, NY: Manning Publications Co.