Qafoo GmbH - passion for software quality

Help you and your team benefit from new perspectives on cutting-edge quality engineering techniques and tools through the Qafoo team weblog.

By Kore Nordmann, first published at Tue, 02 Oct 2012 14:40:54 +0200

Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

Abstract Classes vs. Interfaces

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.

Definitions

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".

Classes are Types

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\FileSystem, 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

An 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.

Telling Apart

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.

But...

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.

Examples & Hints

If you want to train your developers or discuss Object Oriented Design with us, get in contact.

Classic examples for proper interfaces would be:

  • Cacheable

  • Serializeable

I have two mnemonics for people I discuss Object Oriented Design with:

  • interface names should end with able or ing.

    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.

    An interface usually makes sense to be implemented in types, which otherwise have barely anything in common. A prime example again would be Cacheable or Countable, which even is a proper interface defined in the SPL.

    To come up with a "real-world" example: Let's consider an interface 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:

  • Log(ger)

  • Cache

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.

Final hint:

An 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.

tl;dr

An interface annotates a usage aspect of a type / class. Do not just extract interfaces from classes. Use abstract classes, when defining a base type.

Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

Get Technical Insights With Our Newsletter

Stay up to date with regular new technological insights by subscribing to our newsletter. We will send you articles to improve your developments skills.

Comments

  • gggeek on Tue, 02 Oct 2012 16:13:38 +0200

    I'm waiting for the post on how to properly manage constructors when all you want to operate on is an instance of a class implementing an interface.
    Is dependency injection the only solution?

  • Hari K T on Wed, 03 Oct 2012 06:16:18 +0200

    Thank you for the post. You were mentioning about god. Isn't it a typo for good?

  • Rick on Wed, 03 Oct 2012 13:40:38 +0200

    "interface names should end with able or ing"

    Yeh thats a very good point to remember.

  • Daniel O'Connor on Wed, 03 Oct 2012 17:24:05 +0200

    I don't find myself too comfortable with abstract classes - the single inheritance model in PHP is probably the root cause.

    I find in practice that many abstract classes are written first, and the child classes glued ontop of them - the result tends to be not an abstract class and a child type, but a whole lot of child-specific behaviour glued together in the parent class, because the modelling is a bit off.

    Certainly when you start out and refactor to an abstract class, that's going to be a better result - it's likely you know the problems then.

    So, when I see most of the tight coupling exhibited by an abstract class and a bunch of child classes, I get a bit worried - the answer of "just extend and override" is often used; and that's a bad approach too after a certain point - you get multiple levels of "family tree" like structures, and you end up not getting much code re-use out; and if the relatives get into the same room it's always a bit uncomfortable.

    For this reason, using interfaces as something halfway between a mixin and multiple inheritance feels ok to me - you don't get the re-use of a mixin, but you do at least provide a contractual promise of compatibility - mixing that with a fairly shallow inheritance tree, and bingo, life is good.

    Given that the "keep it shallow" approach works so well, I'm almost ready to ditch 95% of inheritance in order to ensure my classes have a single responsibility - and it's entirely defined and encapsulated by only one specific instance of a class.

    I think traits go a long way towards resolving this entirely; it's just unfortunate that I've been over in Rubyland for the past few months and haven't hacked away on any projects with them!

  • Drak on Wed, 03 Oct 2012 18:12:48 +0200

    Abstract classes can have a constructor because the constructor does not define a method signature that must be matched inherited by child classes. It's the only method that doesn't require that same signature on child implementations

  • Toby on Thu, 04 Oct 2012 19:28:54 +0200

    @gggeek: Sound's like a good idea to write a blog post about different approaches to manage DI. Using a DIC is of course not the only solution, however maybe the most convenient one. I hope I find the time to elaborate more on that in a future post.

  • Anil Kumar on Fri, 05 Oct 2012 13:45:52 +0200

    Hi Toby, I agree with you and comments by @gggeek looking forward your future post.

  • Anil Kumar on Fri, 05 Oct 2012 13:49:17 +0200

    Hi Toby, I agree with you and comments by @gggeek looking forward your future post.

  • Jeroen De Dauw on Thu, 25 Jul 2013 14:37:56 +0200

    I have concerns similar to those of Daniel O'Connor.

    On a theroretical level I agree with this blog post. One implication os using abstract classas that I like it that due to the lack of multiple inheritance, it makes certain kinds of SRP violations quite hard to make.

    When using abstract classes, one does open up the door to abuse of inheritance. Especially in codebases with less experienced contributors, it is quite possible people will stuff all kinds of things into the base class that do not belong there. This seems more likely to happen then people implementing both Logger and Cache into a single cache. It is also likely going to be elss obviously wrong, as it can happen in an incremental fashion.

    Somehow using interfaces feels like the safer approach.

  • Akbar Pribadi on Mon, 25 Aug 2014 07:28:27 +0200

    yet im still learning about it

  • Divya on Wed, 02 Dec 2015 15:24:40 +0100

    information given but how to use both interface and abstract class

  • Sofi on Thu, 23 Feb 2017 23:22:14 +0100

    You could add that implementing constants in interfaces is a bad practice, interfaces are abstract constructs should have no implementation awareness.