“Program to an interface, not an implementation” is a well-known mantra from the GoF book. Take this guidance to an extreme, though, and you generate POO instead of OOP. How do know if you crossed the line?
I think it’s useful to take a step back and think about the word “interface” in a general sense. There are interfaces everywhere in software. There are interfaces between layers, between tiers, between applications, between objects, and between callers and their callees. Just about anything and everything in software, no matter how trivial, has an interface.
The real question with interfaces is how many constraints you want in place for any given interface. Consider the following JavaScript code.
function validate(creditService) { creditService.checkCreditForCustomer(this.id); }
The only constraint on the creditService parameter is that the object needs a checkCreditForCustomer function that takes an ID parameter. The validation function doesn’t care how the creditService was built, who built it, where it came from, or what other capabilities might be in place. This code demonstrates the flexible, dynamic, and relatively unconstrained qualities of duck typing. If the parameter checks the credit of a customer like a credit service should, then it must be a credit service.
Static languages generally have to crank up the constraints on an interface, although many have an escape hatch. C# 4.0, for example, introduces a dynamic type.
public bool Validate(dynamic creditService) { return creditService.CheckCreditForCustomer(ID); }
Again - all we need is an object with a CheckCreditForCustomer method that takes an int parameter. Because the object is typed as dynamic, the compiler won’t guarantee what the object can actually do – there is no type checking. At runtime, we may find out the object doesn’t actually support the method we are looking for, and an exception appears. This duck typing behavior is what keeps fans of static typing awake at night. They think the dynamic programmers are insane for throwing around objects in a willy-nilly manner. Meanwhile, the dynamic crowd thinks the fans of static typing are insane for spending all of their time obsessing over types instead of creating software.
Regardless of where you fall in the static to dynamic spectrum, you can view a type definition as a constraint. In C# and Java, the interface keyword can constrain the type of an object without placing any constraints on the implementation.
interface ICreditService { bool CheckCreditForCustomer(int id); bool CheckCreditForCompany(int id); }
Now we can use this constraint to enforce type safety.
public bool Validate(ICreditService creditService) { return creditService.CheckCreditForCustomer(ID); }
An interface (in the interface keyword sense) allows fans of static typing to sleep at night while still leaving some flexibility behind. The object that arrives as an ICreditService on any given call might be one of 10 different credit service implementations. The 10 implementations may be from the same class inheritance hierarchy, or they may not. One might be a mock object or test double used only during testing (which I should point out is not, not, not the point of using interfaces), or it may not. The Validate doesn’t care about the concrete implementation behind the interface.
We still have some flexibility, but we also have additional constraints when compared to duck typing. The credit service has to implement two methods now, even if we just want to build an object for the Validate method which only uses the CheckCreditForCustomer method. These two methods may or may not be good thing. Iterative design with tests and a dose of the interface segregation principle will take care of the matter.
Even more constraints come into play if we use a class definition instead of an interface.
public class CreditService { public virtual bool CheckCreditForCustomer(int id) { // ... } // ... }
Now we’ve not only constrained the type, but we’ve constrained the implementation. Whoever provides our credit service functionality must be a CreditService object, or use CreditService as a base class. Building software is all about composing pieces of functionality together, and using a concrete class as the interface specification places hard restrictions on how the composition will work now,and in the future.
Sometimes, these hard restrictions make sense, or at least aren’t important. For example, classes that have no behavior (like DTOs) don’t need an interface abstraction. I’ve also never found it useful to specify entities using an interface, as they have pure business logic inside (logic dealing only with other business objects or abstractions).
public interface ICustomer { int ID { get; set; } int Name { get; set; } void UpdateAddress(/* ... */); // ... }
In short, you don’t need interfaces everywhere, you need to anticipate where your software needs to be flexible, which isn’t always easy. Using interface definitions between two horizontal or vertical layers of an application is almost always a yes, but programming to an interface between two business objects inside the same context is a definite maybe.
I like to use interface definitions when I want to turn a detail into a concept. For example, I’d feel more comfortable with an business object using an ISendMessage object then an SmtpServer object. The concept is closer to what the object needs to do (send a message), and it’s easier to change the business object’s behavior by giving the object a different ISendMessage implementation. As a special extra double bonus, the object using ISendMessage is much easier to test. List<T> is a detail. IList<T> is a concept.
If you doubt the power of interface programming, then just look at COM. Really. In COM you could only program to an object’s interface, and this allowed objects from different runtimes (Visual Basic versus C), different threading models (objects with a thread affinity versus multi-threaded objects), and different processes (local versus remote) to all work together, plus a host of other features. Interface definitions are the ultimate abstraction (for a statically typed environment!).