When it comes to object-oriented programming there are few concepts as basic and important as interfaces. Only recently though, and at a snails pace, have .NET developers started to take advantage of the simple benefits interfaces have to offer. This interest stems from the core role interfaces play in some pretty hot topics, including: dependency injection, unit testing and mocking (to name a few).
At first glance, interfaces can be pretty misleading: they are so simple and solve such a basic problem that developers often grossly underestimate their value. There is no magic to interfaces, no secret pattern that'll let you squeeze incalculable gains of productivity, they are merely a fundamental part of sound software design which help you build better software.
What is an interface?
The role of an interface is to define the contract that implementers must follow. In other words, an interface says what a class must define. Importantly, an interface doesn't specify (or care) how the implementation is actually achieved. As we move forward we'll see how helpful this can be to us, for now, let's look at a trivial example:
public interface IUserRepository
{
void User FindByCredentials(string userName, string password);
}
public class UserRepository : IUserRepository
{
public User FindByCredentials(string userName, string password)
{
return null;
}
}
The class UserRepository is said to implement the IUserRepository interface. This code will only compile if UserRepository implements all of the methods/properties defined in IUserRepository - with matching signatures (return type, member name, input parameters). If the code compiles, we are guaranteed that an instance of UserRepository can be treated as an IUserRepository.
An interface can define zero or more members (methods or propties) and a class can implement zero or more interfaces. Interface members are always public (I think it would be nice to allow them to be internal though!). An interface with zero members is is called a marker-interface - they don't require implementers to do anything. For example, you might have an IImmutable interface which doesn't have any members; however, your repository might have the following type of code to make use of it:
public void Update()
{
if (_entity is IImmutable)
{
throw new InvalidOperationException("The object is immutable and cannot be updated");
}
//continue with update
}
Decoupling
The raison d'être of interfaces is to provide developers with a language-level tool that allows classes to be decoupled from each other. Mocking frameworks and dependency injection frameworks rely on this core language feature - in a sense, interfaces serve as the main (though not the only) building block for decoupling.
The problem with using a concrete class from client code is that it ties the calling code to that specific implementation. The classic example is calling a data access layer, from:
SqlDataStore store = new SqlDataStore();
to:
IDataStore store = new SqlDataStore();
Many examples would use code like the above and simply state that the latter is better than the former. The problem with this example is that it's rather contrived and doesn't really result in better code. If our implementation changes, say to Oracle, all we need to do is change the first line to define and create an OracleDataStore. (In reality, the better approach to the above code would be to use a DI framework, which would rely on the presence of the IDataStore interface - but don't think that interfaces are only for framework developers.)
I think a better perspective on interfaces is through the extensive presence of collection interfaces found throughout the .NET framework. You may be tempted to use a List<T> to expose the relationship between a user and the one or more roles he can have:
public class User
{
private List<Role> _roles;
public List<Role> Roles
{
if (_roles == null)
{
_roles = new List<Role>();
}
return _roles;
}
}
The problem with this approach is that you're tied to a very specific implementation of a list collection, maybe in a future release you'll find that a LinkedList<T> or a HashedSet<T> is more suitable.
Before we jump to an interface though, let's take a realistic look at the last statement, which essentially boils down to: you may want to make a change in the future. If you and your team are the only people writing code that consumes the User class (which is likely), changing the roles implementation from a List<T> to a HashedSet<T> isn't going to be a big deal. Its worth repeating that decoupling through interfaces leads to small incremental improvements of your system - it isn't a silver bullet.
We can decouple client code from the specific List<T> implementation by making a dead-simple change to the User class:
public class User
{
public IList<Role> _roles;
public IList<Role> Roles
{
if (_roles == null)
{
_roles = new List<Role>();
}
return _roles;
}
}
Except in a few edge cases, there's never a good reason to expose the concrete implemented over a an interface.
Promise Less
One of the benefits of exposing an interface is that you can promise the minimum amount of functionality needed by calling code. This may sound counter-intuitive, but the less functionality you promise, the less you need to maintain. In our above code we may use an ICollection<T> instead of an IList<T>, which defines even few methods (yet still offers all the core stuff you'd likely need with a collection). We might even take it a step further and expose an IEnumerable<T> which would only let calling code iterate through a user's roles. Adding and removing roles might be accomplished through appropriate methods in the User class (in this case we'd likely find that IEnumerable is too limited though).
Yet another benefit of promising less is that it's easier to build new implementations. Using a IList<T> allows us to change the implementation between a handful of built-in .NET classes (or write our own). Exposing an ICollection<T> allows us to use even more built-in classes; and of course exposing an IEnumerable<T> let's us swap the collection implementation with virtually any of .NET's collections.
Whether you ever actually build your own, or ever swap out the implementation isn't really the point. The point is that the ability to painlessly do this is a symptom of a quality system. It isn't the end of the world if you can't, it's just better if you can.
Abstract Classes vs Interfaces
There's some overlap between abstract classes and interfaces. Sometimes it isn't clear which tool you should use. A pragmatic factor that can help you decide is that a class can implement multiple interfaces, but only inherit from a single base class. Generally speaking though, an interface isn't concerned with implementation, whereas an abstract class typically provides a set of common behavior to inheritors. The two often play quite well together. Take a look at a real implementation of the UserRepository we fist looked at:
public class UserRepository : BaseRepository<User>, IUserRepository
{
public User FindByCredentials(string userName, string password)
{
var query = new Query<User>().Where("UserName", Operations.Equals, userName).And("Password", Operation.Equals, password);
return FindOne(query); //FindOne is defined in the BaseRepository
}
}
Client code programs against the IUserRepository, while the actual implementation re-uses the base abstract class functionality.
What to Interface?
So where do we draw the line? Should we program against IString and IInt32? The answer to what should and shouldn't be decoupled via an interface comes from experience and varies from system to system. There's a realistic limit to what we can and need to do. As you can guess from my example, collections are generally worthy of decoupling, as is code that sits on a layer's boundary or serves as part of a published API (whether or not you're the only consumer).
It's worth pointing out that one of the benefits of unit testing is that things that need to be decoupled quickly become evident.
Conclusion
Making use of interfaces today is a quick and easy way to make your system better for tomorrow - it costs absolutely nothing, yet rewards you with greater flexibility. Instead of returning Dictionary<T, K>, consider returning an IDictonary<T, K>; instead of having an input parameter of type SqlDataReader, maybe you'll get more re-use from specifying an IDataReader or IDataRecord. Sadly you'll notice that most of the documentation/books/guides you'll find don't make use of this simple pattern - even though it's long been established in OOP circles (the concept of information hiding, which is essentially what we are talking about, was formally being discussed in the early 70s). Simply put, don't expose implementation details unless you have to.