While teaching a course on design patterns I was asked by one of the participants when would I use an interface and when to use an abstract class. I've started the outline the reasons for each and ended up with a few good ones which I like to share in the following post.
Most object oriented languages have the ability to define both interfaces and abstract classes. Even languages that have only abstract classed it's usually simple to implement interfaces by defining an abstract class without any method implementation (I'm looking at you C++).
And that's the main difference - while an interface defines a contract,, an abstract class (or pure virtual class) have several default behaviors implemented as well.
At first it does not look like a big issue – either define an interface IMyInterface or define a base class MyAbstractBase with the same methods. The real issues start further down the road - usually when a new class introduced into the system.
The reason the question came up during my design patterns course is that one of the developers had an issue in his code: a few versions ago they had created a base abstract class as the and created many inheriting classes in an important part of their application. And so a few months went by and they discovered that while some of their classes needed to keep the old functionality – the a new class needed to perform similarly defined methods/operations using a completely different implementation. Unfortunately at this point it meant making a lot of changes to their existing code which could break existing functionality - and no one wants that! So in retrospective they wished they have used interfaces when passing object and that way new behavior can be introduced to the system without many breaking changes - right?
Using interfaces instead of classes when defining dependencies is usually a good idea.
Not all OOP languages were created equal: For example in Java all methods are overridable unless declared final while C# and C++ it's the other way around. This means that my ability to override a method depends on my language of choice. It also means that down the line, I might need to use external libraries which relay on the ability to override methods we might discoverer that we failed to make the right methods virtual. And so using ORMs, IoCs and Mocking frameworks (to name a few) makes our base classes look weird with some methods virtual and might cause even weirder behavior.
In Java where all of the methods are virtual unless declared otherwise there are a different set of problems - namely other developers inheriting classes and changing functionality in ways that were not anticipated by the class creator. And so defining an interface as a way of defining a clear contract between an object and its dependencies is a good way to make sure that it can be replaces easily without "paying" the price for previous implementation. Unfortunately it also means that every time I write new class implementing that interface I needs to figure out all of the needed functionality - a problem that can be avoided with a base class that implements common behavior - "free of charge". It seems that the best solution would be to use both an interface and a base class that implements it and add the common desired?
The textbook solution is to have an interface which is passed around and referenced throughout my system as well as a base class implementing some of the common functionality. This means I can use the base class or not - according to the project’s needs. This way you do not have to commit to a specific implementation in case I do not need it. The price in this case is in maintenance. Every change to the interface needs to be reflected in the base class - new methods, deleted methods and so on.
It's not a huge price but this kind of work tend to become tiring real quick, and developers will end implementing one of the solutions described above and we’re back to square one.
The end of this debate we’ve ended up with more questions than answers but we did found a few ground rules – when in doubt use an interface and if use abstract classes when refactoring duplicate code. And in any case never-ever name your classes with the suffix “Impl”
Labels: Design, Thoughts