Timeline Taxi Out now: my sci-fi novel Timeline Taxi is published!

Extend or implement

I'm probably part of a small group that actually cares about this: the difference between extending an abstract class and implementing an interface (ideally with a trait to provide part of the default implementation). I don't expect many people to follow my reasoning, but hey, it's my blog, I can write whatever thoughts are coming to my mind 😅

I also made a video about it, by the way:


I've been wrestling with code that abuses inheritance for a couple of years now. Granted: I don't really like the word abuse because it sounds very heavy, and code that abuses inheritance can still work perfectly well. But I also struggle to find a better word for it. Maybe "code that doesn't fit my expectations of what inheritance should be used for". I'm frustrated with myself because I can't seem to put into words exactly why I dislike code that's intentionally designed to be extended. It happens a lot in Laravel, but I'm sure it happens in Symfony and other places as well.

"Extend from a base class", "override a method" — all in the name of "flexibility" and "configurability". It's a common pattern, but it feels so… icky to me. The practice is so common that a whole community of developers is vocally against using final, because it blocks them from accessing that flexibility inheritance is giving them.

All the while, I'm thinking "this isn't the way inheritance was meant to be used…"

Like I said, it's difficult to put into words, but I think the problem for me started when I watched Alan Kay's talk about his vision for OOP. Alan is the inventor of object-oriented programming, by the way. He describes how you can build a dog house using only a hammer, nails, planks, and just a little bit of skill. Once you've built it, you've earned the skills and know-how, and you can apply that knowledge to other projects. Next, you want to build a cathedral, using the same approach with your hammer, nails, and planks. It's a 100 times larger, but you've done this before — right? It'll only take a little longer.

This is where Alan says OOP started to derail: languages like C++ took a relatively simple concept, and started to do a bunch of things with it in a way it wasn't supposed to be used. Granted, the code works, but it's lacking in many areas. Honestly, it's a fascinating talk and I highly recommend watching it.

Back to modern OOP: we've taken a concept like inheritance, and made it almost equivalent to "an easy way of configuring and plugging into vendor code". Open source packages are designed deliberately to extend and overwrite different parts of their codebase, hoping all goes well. The sad part, to me, is that there are better solutions to solve these problems. Other ways than to rely on inheritance. There's a range of patterns that help us solve whatever issues we run into — including configuration and "plugability" of code that's out of our control. Alan talks about this as well, by the way: proper architecture is a thing.

I was thinking about inheritance, comparing it to interfaces, and I came up with a definition that somewhat makes sense to me: I want to use inheritance to reflect real-world relations; I want to use interfaces to describe technical behaviour.

A class that implements an interface doesn't necessarily describe the essence of that class, it only promises it can perform the tasks defined by that interface. It could do a lot more things, for all I know, but I don't care about that within a specific context. It's a "hey, I can do this" promise rather than "hey, I am this". Inheritance, on the other hand, conveys that a class is a subcategory of something else. To me, that kind of relation only makes sense when we're talking about real-world modelling. Or maybe it's the other way around: if we try to apply that kind of relation to technical properties, things get out of hand fairly quickly.

An example: User extends Model, for me, that's wrong: a User isn't a Model; instead it's a class that can act like a model. It's a class that makes a promise to whatever context it's being used in. AdminUser, on the other hand, should extend from User, because it reflects a real-world relationship: an AdminUser is everything a User is, and probably a bit more. So, in my mind it should be AdminUser extends User implements Model (although the implements Model already happens on the parent class and isn't necessary here).

Like I said, I suspect not many people caring so deeply about this, and I'm not sure why I care so deeply about it myself. I think this definition keeps things somewhat clear to me. It eliminates a gray zone that I find confusing to work in.

And sure, there are some technical arguments to make against my thought experiment within PHP: there's no multiple inheritance, there are no interface default methods, and you can't implement an interface via a trait. There are limitations to the language we're using.

But still, I find this mental model more comfortable than the alternative.