By Kore Nordmann, first published at Tue, 02 Oct 2012 14:40:54 +0200
Features of object oriented languages are often use from a purely technical perspective, without respect to their actual semantics. This is fine as long as it works for you, but might lead to problems in the long run. In this article I discuss the semantical differences between abstract classes and
interfaces. I also outline why following the semantics of those language constructs can lead to better code.
Disclaimer: This is of course kind of artificial. If using the language constructs in other ways works for you, it's fine. It can make communication, extension of code and maintenance of code harder, though.
First, we need to differ between
interface and interface. When typeset in mono space I mean the language construct -- otherwise I talk about the public methods of any object / class. The second one often is also called "public interface", "class interface" or even "class signature".
A class denotes a type.
We know that objects consist of internal state and methods to operate on this state. The interactions between objects are implemented by calling methods on other, related objects. Those methods might, or might not, modify the internal state of the called object.
Classes, as they define the blueprints for those objects, define the type of objects, thus we know how to use an object. When talking about types there are some natural implications, which apply especially when extending from another class, which means to create a sub-type. One is the Liskov Substitution Principle:
“Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.”
This basically translates to: "Do not mess up the interface". It is required, so you can use a sub-type (derived class) without any special knowledge, just by reading the documentation / interface of the parent class. Imagine a
Cache class, from which you extend a
Cache\Memcached and maybe a
Cache\Null. You do not want to implement any kind of special handling in every place where an object of any of those derived classes is used in your application. All of them should just behave like a
Cache. Keep this in mind.
If we can use any sub-type of a type just as the parent type defines, we talk about Subtype Polymorphism. Something you definitely want to achieve. Abstract classes are naturally used when you need such a parent type, which requires some specialization, like the
Cache class mentioned above. I'll get back to why it makes no sense to extract an
interface from this. And yes, you are allowed to define abstract classes, even if you don't intend to implement any code already and don't have any properties to define.
interface describes an aspect of the interface of a type.
PHP does not support multiple inheritance, but it allows you to implement any number of interfaces, right? The reason for this is, that an
interface annotates usage aspects on the interface of a type. The Interface Segregation Principle also claims:
“Clients should not be forced to depend upon interfaces that they do not use.”
This means that a type using another type should only depend on an interface, which is actually used. Depending on a god class, for example, is the exact opposite. You would not know which methods are actually called from looking at the injection signature. But this also implies that the interfaces should be designed small and task-specific.
To stay in the context of the example mentioned above, a proper interface would be
Cacheable to annotate on certain classes that its instances can be cached. The natural counterpart of a
Cache, if you want to handle objects. This is a minimal usage aspect of a class and other classes requiring this
interface will most likely, and may only, use the methods required by this
interface. Implementors of this interface would also not be forced to implement meaningless methods.
Those definitions are nice, and fluffy -- but what happens if you define an
interface instead of using an abstract class? (This is at least what I see most.)
You invite others to create god classes
Interfaces do not allow to already provide code or define properties. Their only "benefit" over abstract classes is that you can implement multiple of them in a class / type.
So why would you define a
Cache interface. Obviously you do not want that some class implements the
Cache logic while also being a
Logger and a
ORM -- even all might access the same storage.
While this, in theory, provides more "flexibility" for the future, every usage of this flexibility would hurt your software design -- a lot.
You overload interfaces
A common developer pattern is to define a type, and then extract one single
interface from the class interface. Such interfaces do not define a single usage aspect any more. If someone wants to implement one aspect of this
interface and two or three aspects from some other "
interfaces" it would result in one really big class with dozens of empty methods. Or, even worse, dozens of implemented methods, which are never used. This would obviously be wrong.
There is this one popular quote:
“Code against interfaces, not implementations.”
"Interface" is not typeset in mono space. Look it up!
To phrase it better, like it is done in the Dependency Inversion Principle: “Depend on abstractions, not on concretions.”. "Interfaces" in the above quote may very well be abstract classes. Don't forget about those.
If you want to train your developers or discuss Object Oriented Design with us, get in contact.
Classic examples for proper interfaces would be:
I have two mnemonics for people I discuss Object Oriented Design with:
interface names should end with
This is obviously not always true. But ending on
able is a strong indicator that such an
interface just annotates one usage aspect.
interfaces make sense to be implemented in entirely different types.
interface usually makes sense to be implemented in types, which otherwise have barely anything in common. A prime example again would be
Countable, which even is a proper
interface defined in the SPL.
To come up with a "real-world" example: Let's consider an
Drinkable. You could implement that one on cups, on the sea and maybe even on humans, if there are vampires around. Otherwise humans, seas and cups probably do not have that much in common.
Examples for proper abstract classes could be:
Again I have a mnemonic to help you with the decision:
An implemented abstract class can stand on its own.
If you implement all remaining abstract methods of an abstract class, you get a proper member in your world. It does not require any additional methods to be usable by others. It probably will require some dependencies (like a storage), but most other objects will happily just call the methods provided by the abstract class.
interface must not define a constructor. Never. The same is true for most abstract classes. By defining a constructor you predefine and limit what dependencies may be injected in implementations. It is very likely to change for different implementations. But this is actually a topic which deserves its very own blog post.
interface annotates a usage aspect of a type / class. Do not just extract interfaces from classes. Use abstract classes, when defining a base type.