Inheritance is Bad: Code Reuse Part I
- Inheritance is Bad: Code Reuse Part I
- Inheritance is Bad: Code Reuse Part II
- Inheritance is Bad: Code Reuse Part III
A little time ago i commented on a blog on this site and gave an description about why Roles are better than inheritance. Or better, why i think so. That was how some people interpreted my comment, but my comment was more about "Inheritance sucks". I talked about Roles because it was the topic. So now i decided to do a series of "Inheritance is Bad". And i want to give different reasons and description for it, why i think this way. But only complaining isn't so much productive, so i also want to give examples what you can do, to make it better.
2. Code Reuse
The first thing i want to talk about is Code Reuse. I think Code Reuse is probably one of the most important part why we use Object Oriented Programming in general. And more specific, a lot of people try to archive Code Reuse through inheritance.
The concept itself looks very easy. You have written a class and some parts of your class like some Methods or Attributes should be reused in another class? Then often in introductions, Tutorials, Books and so on you often just read: Create a super class. Put your code in your super class and inherit from that class. Now you can create your new class that also inherit from that super class. That concept sounds really easy. So easy, that often other ways to archive code reuse will never shown, and a lot of programmers don't even think of solving it another way. Just for example pick the "Object Oriented Perl" Book from Damian Conway. It's just a book about Object Oriented Programming in Perl, over 400 pages. And something like "Composition" and "Aggregation" is only mentioned as a site-node.
At some points it also looks religious. I often read comments like: If you don't use inheritance, then you don't programming OOP. Or pointing out problems with inheritance get a lot of people angry. It often seams that criticizing inheritance some people feel that there beliefs attacked or something like this. So i hope something like this will not happen hear. What i am telling you is just my experience. I don't want to attack someone or insult someone. If you have another opinion and you think my examples are in some way wrong. That's fine. I don't force anybody that he must think the same things i do.
Before i start, you probably want to knew what i think how bad inheritance really is. Well, in my opinion inheritance should be removed completely from the Object Oriented Programming paradigm. That sounds extreme but that is just to point out to say how bad i think inheritance is. And i am not the only one that thinks so.
The Java Creator was once asked what he would change when he reinvent Java today. He answered that he would remove inheritance. Also Google's Go Designer did not implement inheritance. And now that leads to the question. What is wrong with inheritance?
3. What's wrong with inheritance
It looks like irony when we start looking why inheritance is bad. That is because the best way to show what is wrong with inheritance is when we use the exact same example that is often used to show why inheritance is great. We describe it with Animals.
To start our example we want to create two Animals. A Dove and a Tiger. What we now could do is create our base class "Animal". All our Animals inherit from that, well, because they are animals right? We can now put everything into Animal what every Animal has in common. Lets say we create a game, so we put "life" into Animal, an boolean that say if our animal is alive or not. Every animal has a name and so on. Our inheritance graph now looks like this
Now we want to add some special behaviours to the Dove and the Tiger. Both the Dove and the Tiger can walk, and the Dove can Fly. So our inheritance looks like this
Now we want to add a Parrot. We now see that a Parrot also can fly like the Dove, so we want to reuse the already written code. Putting the flying code in Animal is wrong, because then also the Tiger could fly. So we create another superclass. The Bird.
Now we want to add the Goldfish. So we can create another subclass "Fish" from Animal. But where do we put our "Walk" Code? Because our Goldfisch that is an animal cannot walk. So here is the first problem. Finding a description for animals that walk. And i really don't have a clue how to name them. So lets just name them "mammal". Even if a Bird usally is not a mammal. But we don't reprogram the reality, right? (If you have another better description, feel free to add it in the comment). So now our inheritance graph looks like this.
Now we have some inheritance graph that looks somewhat usable. We can now creat a lot of Birds that now can inherit from "Bird". And they all have in common that they can fly. We can add Lions, Zebra, Monkeys and so on to the "Mammal" Class. And then we can add our Dolphins, Sharks, Octopuses to the Fish Category because they swim.
Well Dolphins are mammals too, but we still put them under Fish, because Dolphins Swim. I mean, don't be so nitpicky right? What we really want is that our Dolphin gets our Swim Code, not that every word is exactly corret, right? Yes i knew Octopuses are also not fishes, but that is just nitpicky. The important part is that our Octopus also can swim, and we used proper Code Reuse, right?
So lets add some other animals. I like Penguins. So lets add a Penguin. Hmm, where do we put it? A Penguin is Bird right? But well, a Penguin can't fly even the fact that he has wings. So we put it under Fish right? Because a Penguin can swim. But wait, a penguin can also walk. So actually we need a new subclass once again! Oh damn...
Okay, now we have to implement Flying Fishes. Oh damn, that think can swim and fly? Crocodiles that also swim and walk? And now we also want to implement the "American Dipper". What is an American Dipper? Oh seriously? A bird that "flys" under water or better swims? But still walks and fly? Really, who created all that mess?
4. Inheritance recap
Okay, now jokes aside. But i hope you start seeing the problem with inheritance. We usally create "abstract classes" and put code in them. And with "abstract" i don't mean "abstract classes" like in Java oder C#. What i mean is abstract that we name it "Bird", "Mammal", "Fish" or something else. And what do we do with it? We link specific behaviours to that abstract class names. For example we say that a Bird can Fly. So everything that should fly needs to be a bird.
But the problem that we sooner get is that we have some animals that also have the behaviour "Fly", but they are not Birds. For example the flying fishes. Even a "bat" is not a bird. Birds have "Feather". And also birds are not mammals. But we have mammals that still can fly like the mentioned "bat".
If you look at it then it just seems extremly complicated, but just look what we wanted to do. We just wanted to implement different Animals. And the only thing we wanted was to describe that some Animals can swim, fly or walk. So why is this such a big problem?
Well because instead of describing exactly that "A Penguin can walk and swim" we tried to invent some abstract classes like "Bird". And then we suggested that every Bird can fly, but that is not the case. The problem is, we link abstract names whit exact behaviours. And that is just wrong. Yes, 90% of the birds can fly, but not every bird. And that is why we run in problems.
But lets rethink. Why do we do all that stuff? What was the purpose? Did we wanted to describe that a penguin is a bird? Hell no, nobody wanted that. Our intention was to have Code Reuse. We wanted just to write our Flying code, and we wanted to reuse our code so that we don't have to rewrite it again.
But the big question is. Why do we try to think abstract with Birds, Mammals, Animals or Fishes. Why do we not just Code the "Behaviours" directly?
Okay, so now we knew its better to think in Behaviours. We should Code something and describe exactly what it does. So we have Code that implements "Flying". Then just name it "Fly", "Flying", "Flyable" or whatever name you come up with. But name it "Fly", not "Bird". So we have three things we want to reuse. Flying, Walking and Swiming. And now lets say we want to Describe our Dove, Tiger, Penguin, Goldfisch, Flying Fish, Bat.
Dove -> Flying, Walking
Tiger -> Walking
Penguin -> Walking, Swiming
Goldfish -> Swiming
Flying Fish -> Swiming, Flying
Bat -> Flying
So, is that describen above anywhere complex, not understandable? I think it is by far more easier then trying to put in an inheritance graph. You just name the behaviours what exactly they do, and put only that code inside this behaviour. And even if you want to add something. A Bat can also walk/swim? Just add that behaviour to the bat. Done. And now lets even think further. We want to implement the T-800, submarine, tank and a helicopter
T-800 -> Walking
Submarine -> Swiming
Tank -> Walking
Helicopter -> Flying
Any Problem? Where would you put your Helicopter in your inheritance graph? Under Bird? But one question is still open. How do you implement these Behaviours exactly?
There exists two ways how to implement Behaviours. The first way is, if you "abuse" multiple inheritance with it. You just create three classes and name them after your behaviour "Flying", "Walking" and "Swiming". Then you are able to just reuse your classes as much as possible. Now you can just create your Penguin class and inherit from your "Walking" and your "Swiming" class.
Well, now we use inheritance again, but the important thing is to make clear how you use inheritance. What you are doing is using classes in two ways. Some classes are just "templates" or a way to archive your code reuse. But these classes will never be used directly. For example you never create an object directly from the Flying class. Another important thing is that these templates are named after your behaviour. And even more important. Every template should only implement one Behaviour.
You also find examples of these usage directly in Perl. For example the IO::File class. It inherit from IO::Handle and IO::Seekable. So we have the behaviour "Seekable" and "Handle".
The reason why this is "abusing" is because you will never for example directly create an object from IO::Seekable. And even in the inheritance graph before you will probably never create directly an object from Bird. The only reason why you had these classes was to have your code reuse that you don't need to write your flying code a second time.
But a better way that makes it clear that you have reusable code is to use Roles instead. A Role does exactly that. Think of behaviours. Put your code in a Role. Now you cannot inherit from that Role, you cannot create an object from that Role. But you can insert that Role in another class.
Yes, you can achieve the same thing without a role. But i think it is really important to use a Role for that. It is the same reason why it is important to make a difference between a function and a method. Technically both are identical in Perl. But naming the one thing method makes it clear that you call that method from an object instance. Calling something a Constructor or a class Method is also important. Even if Perl don't have different keywords for it. It make it clear to uses that name in the documentation. So somebody knews a constuctor creates an object. A Method is called from an object of that class, and you can call a class method directly from the class, even if in Perl everything is just a function.
And using Roles will also help you in thinking of different "Behaviours". And it will help you, because everytime when you inherit from something you think instinctively: "Oh wait, why do i want to inherit from it?" And often you see that you just want to reuse some code somewhere that you better put in an Role.
It will also help you to faster understand what a class is capable of. Because everything is flat. You just have your class, and your class have a list of Roles. You don't have a Penguin that inherits from "Bird" and/or "Fish". And that Bird inherit again from mammal that inherits from Animal and so on.
So if you think in behaviours and use roles for code reuse you will get a much easier and much more powerful code base. You can easily reuse code. And you can reuse code in a way that you probably never thought when you started your project. Like the helicopter that also can fly and don't needs to inherit from a "Bird" class or something like that.
But i also want to mention that roles are not the holy grail. They are just another way to achieve code reuse, in my opinion it is a better way as to use inheritance, but also roles have some problems. In my next Blog i will talk about the problems with Roles. But it is still important that the concept about "behaviours" will not change. In the next Blog i will show you how you can achieve an even higher level of code reuse that is even more flexible without roles.
Your comparison with IO::Seekable as a role is somewhat of a red herring here.
The IO::Seekable class exists, not because it's a sensible split in code structure, but purely for social reasons, as it was easier to create a new class than to try to get the seeking methods into IO::Handle itself, where they really ought to live.
It's a historical anomaly that exists because of spurious human reasons, not technical design ones.
I did not say that IO::Seekable exists or was split because someone had the design in mind that i described here.
I'm justing saying that the design that i described matches that of IO::Seekable and it is just an example to better understand what i described.
Please continue :)
I will continue. I got some personal problems here. But next article will come soon.
OOP solves the problems you describe with interfaces. Clean, simple, no muss, no fuss.
interfaces solve Part of it. The Problem is that interfaces itself don't have Code Reuse because their are just interfaces.
In Part II i describe a solution with Roles. That somewhat gives you what interface have plus an implementation.
But in Part III i also explain the drawbacks of Roles and that it also makes sense to use Composition instead of Roles.
Term you are searching for is tetrapod (or Tetrapoda), which includes amphibians, reptiles, birds, and mammals :).
See here on Encyclopedia of Life: http://eol.org/pages/4712200/overview .
BTW, Nice series of articles.
"Tetrapod" is only for 4-legged animals. For example a snake can also swim and move on the land, but is not a Tetrapod.
I think one of the main thing i want to explain here is: Even if you find a term to describe something it makes more sense to just use the behaviour names. Because the pure simple behaviour names will never "collide" with something. That means you want to use the behaviour coded in a class but it doesn't make sense to inherit from it.
If you want to reuse your "swim-movement" in an u-boot you just can do that. Inheriting from "Tetrapod" will probably not make much sense.
Another thing is. If you name your classes just like your behaviours it is clear what a new class does. If you inherit from "Tetrapod" it is unclear at first what that really means and which behaviours you will inherit.