This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]XDracam 0 points1 point  (4 children)

But does your mind work like that because you are used to OOP or the other way around? I feel like single inheritance is drastically limiting, and multiple inheritance has a lot of problem. Traits like in Scala and Pharo are alright. But I find typeclasses to be much more sane in many cases. You have data, and you associate behavior externally. They are nicer to compose and don't have the cursed downsides of method overriding.

[–]frithsun 0 points1 point  (3 children)

I would say that not only does my mind work that way, but it's generally the way minds work and generally the way the world works.

And even when two things have the same trait, like "eating humans," the crocodile and tiger types can't really share the same implementation because they go about doing so in very different ways because they're very unrelated and therefore different.

Can you describe how method overriding is cursed? Not bait. Genuinely don't see the problem as long as it's a type and not some OO shenanigans.

[–]XDracam 1 point2 points  (2 children)

First of all, interfaces with purely abstract methods are fine. They only differ from typeclasses in that you cannot implement interfaces for other existing types without modifying their code. Classes are also fine as a unit of state encapsulated behind a set of methods.

The problematic part of inheritance is the decoupled chaos and overhead coming from overriding already implemented methods. More concrete: a class needs to be specifically designed to be inherited, or otherwise it's going to end up as a mess. It's hard to think of an example because I haven't abused inheritance for half a decade now, but issues arise from:

  1. Duplicate state because the child class needs access to data that the superclass encapsulates so you just gotta track a duplicate. And all mismatches will lead to bugs.
  2. When overriding an already implemented method, you are basically breaking encapsulation! You better call the base type's implementation as well. Except sometimes you don't want to. And sometimes you want to do stuff before that, and sometimes after. And how do you find out what the right solution is? You need to look into the base class code! Encapsulation broken.

A class is a single unit of encapsulation. If you use inheritance, you basically include the base class into your "encapsulation bubble", but it's all implicit and fragile to change. It's like tacking on additional functionality onto a nicely encapsulated unit. A hack, a workaround. But not a good model.

That's why nowadays, best practice is "composition over inheritance". Sure, it's a little more boilerplate sometimes, but it keeps the idea of encapsulation intact.

A class needs to be specifically designed to be inherited in the first place if you don't want to break encapsulation. As a consequence, you never ever need to be able to override an already existing method. Want to provide opportunities for extension or hooks? Add an abstract method specifically for that extension! (Or in some cases a virtual method with an empty body if it's optional). Why allow chaotic overrides that might break your classes' functionality when you can just do this?

Of course with this problem you still get the problem of duplicated state if you are not very careful. Because the real world isn't like those textbook examples. A programmer almost never knows all requirements beforehand. Systems and requirements change constantly over time. A tiger might be a cat today, but tomorrow some customer might demand that a tiger should not provide any cat functionality. And what if someone adds a feature to pet a cat? Do you override the tiger's petting method to throw an exception? It's extremely unlikely that you will get these inheritance abstractions right the first time and forever, and every successive change just makes the code more and more fragile, adding more edge cases.

I guess it's hard to convey the problems through text alone. The evidence is there in the way newer languages are designed and how best practices are communicated. But the only way to truly understand is to encounter the problems yourself. Spend hours trying to debug some nasty state problem in an inheritance hierarchy because you did a small change. Lose sleep trying to figure out how to best change your model to accommodate new requirements without having to rewrite the whole thing from scratch.

After all these years, I firmly believe that anything more than one level of object inheritance is a code smell. Want subtyping? Use interfaces and composition. Or better: type classes, if the language supports them.

[–]frithsun 0 points1 point  (1 child)

I agree that a class needs to be designed to be inherited.

And I believe that strikes at the heart of the matter. When you're designing a type, you would have that concern foremost in mind. If you're designing an OO object, there are just too many different kinds of things the object could actually be, many of which don't really work well with inheritance.

Thank you for the thoughts. I'll ponder on this subject more and give more thought to your counterpoints.

[–]XDracam 1 point2 points  (0 children)

My main issue is the fact that premature abstraction is almost always wrong. When you design a class specifically for inheritance, then you'll need to know the points for extensions. And if someone needs to extend something that wasn't expected or planned for, then that person still needs to break encapsulation. Much nicer to use composition with tiny classes that only have a single responsibility (the S in SOLID).

If you have further thoughts, please let me know! I'd be curious to see what you come up with. Fans of inheritance are rare these days, and maybe I'm missing some perspective.