Use traits and you get this for free, without implementing all the plumbing yourself.
Inheritance has its place in game dev though. It's just that your hiearchy needs to be designed in the space of what you are actually building: not vehicles, weapons, and monsters but renderers, physics, AI, GUIs, etc. And you want to figure out how that stuff is going to work before you break it up into classes.
I don't come from a game development background, but I do find the realm interesting, and I'll probably have my crack at it soon enough.
This post is interesting, it kinda goes against what I think of when I think of OOP. I would have modeled it with inheritance of vehicles, weapons and monsters. But after reading this post I could see how that could be a bad idea.
But I'm curious in what you say. "Hierachy needs to be designed in the space of what you're actually building: renderers, physics, AI and GUIs." Do you care to explain this farther? Why those over the concrete objects? What do you even mean by it?
EDIT: Specifically how what do you mean by having inheritance of physics, renderers, AIs and GUIs?
When I was in college, I was taught OOP in Java via the obligatory inheritance examples: "So we have an Mammal class which has a 'walk' method, and then we have Cat and Dog classes which inherit from Animal, and have 'meow' and 'bark' methods."
This is a sadly ubiquitous anti-pattern which will cripple your ability to do OOP until you unlearn it. Never model the actual domain. Doesn't matter if it's a game ("okay, so the PlateArmor class inherits from the Armor class") or a CRM ("okay, so the Comment class will inherit from the FormattedContent class").
Instead, model how the program should work. How to do that is a bit beyond the scope of a comment, but a good start is to think about behaviours and interfaces that logically fit together.
(And, again, this tends to be a divisive issue. But I - and a lot of very good programmers whom I respect - strongly feel that the "Cat inherits from Mammal" example is exactly and precisely what you should never do. Despite the fact that it's how OOP is taught in most courses and books, seemingly.)
For completeness, Square inheriting from Rectangle is horrible for at least the following reasons:
1. Inheritance is IS-A. A Square ISN'T-A Rectangle because it breaks Rectangle's contract. If I give you a Rectangle object, you know that you can change the width without affecting the height. If I give you a Rectangle reference which happens to be a Square instance, setting different width and height will either cause a crash/exception, which is not what a Rectangle should do, or ignore one of the values and set the height equal to the width, which is also not what a Rectangle should do.
2. A bit subtle, maybe, but important. While your renderer probably has a method taking Shape objects, it is very unlikely that you have an interface somewhere that takes Rectangle objects. In that case, what you actually need out of this is some code reuse between Rectangle and Square, not type inheritance.
It's also interesting that this Rectangle/Square conundrum disappears when you remove mutatable state from the picture. I think it's usually best to model mathematical objects like these as simple value objects anyway, it's less surprising and more similar to how they are treated within mathematics itself.
The conundrum is that you can't define a canonical inheritance relationship between square and rectangle or mammal and cat because the relationship depends entirely on how you are using these concepts in your program. Making them immutable may affect the relationship but does not make it canonical.
It's mostly stuff I picked up from practical experience and talking to other programmers, so I don't have any links offhand. Let me expand a bit though.
The "wrong" way (or what I view as the wrong way) tends to be common in academic settings, and (seemingly) among Java programmers. It is often contemptuous or wary of multiple inheritance, and it follows the Nygaard Classification[1] "A program execution is regarded as a physical model, simulating the behavior of either a real or imaginary part of the world."
This definition makes sense when you realise it was created by the inventors of Simula to describe their OOP simulation language. Unfortuantely, Simula was far more influential than it deserved, because OOP is terrible at simulating things[2]. When you create a class structure based on the actual simulation you (1) will have a bear of a time doing it, (2) will find multiple inheritance will melt your brain ("okay, so Black and White inherit from Color, and Zebra inherits from Black, White, and Animal...") and (3) will find than once your done you haven't actually solved your problem. (Not surprisingly, Simula didn't have multiple inheritence, and language without that feature often seem to gravitate to Nygaard-style OOP.)
So much for what not to do. What about the right way? I don't really have any links, but good principles are: Focus on interfaces and behaviours. Classes should be abstract (in the common sense, not the language keyword), not concrete. Understand how your program works, break it down into functional areas, implement each area as a class. If you find common behaviours, abstract them into base classes which you mixin where you need them. Inheritence trees should be flat, minimal, and almost an afterthought. Also, a language like Python (with duck typing) is a lot easier for most people to "get" OO than Java.
Finally, the way it was explained to me that really "clicked" is this:
"Where I think most introductory courses in OOP go wrong is introducing objects as being nouns rather than a collection of verbs. That leads directly to improper use of inheritance. I was misled for years by the 'is a' idea. I wish someone had told me 20 years ago that it was 'has the behavior of' that was important."[3]
I think that's exactly right. But do keep in mind that the world is full of people who think that's a heretical view and the Nygaard Classification is the One True Way. :)
I just mean that first you need to figure out how your engine is going to work -- what actual code you are going to write -- then go about organizing that code into an object model.
A physics engine will have classes for the things it cares about like masses, constraints, collisions, contacts, etc. A renderer could have things like meshes, materials, particle systems, and lights. Each subsystem has a different idea of what a particular vehicle, weapon, or monster is. Two monsters might have the same physics properties but be rendered very differently. A Koopa and a Flying Koopa might look the same but behave differently. You can't start with classes for vehicles, weapons and monsters, and expect these classes to be used across all subsystems.
OOP is really a way to organize your code. OOP doesn't generate solutions.
The general idea is that your objects should model the program, not the domain.
Games may blur this line since they are simulations in some respect, which is a situation where you actually do want to model the domain. That doesn't mean you need anything as unsubtle as classes with a 1:1 correspondence to domain objects, though, which is what this article is about.
I think the problem exists because people hear "object" and they think of physical objects, when, as you say, the objects in OOP are supposed to be something more abstract: a means of gathering together common code and common data, not a means of creating digital mirrors of real objects.
Objects when not doing a simulation are merely syntactic sugar of keeping the data and the functions that act on them very close to each other in a very tightly coupled way, while keeping other functions more loosely coupled.
Trying to simulate things that don't require simulation (aka, modeling physical properties), is a bad idea. You're not really making a program then, but a very specific, complicated subset that requires way more work and doesn't get your job done.
Composition may be done at runtime, but indeed changing the behavior of an existing object at runtime is not something trait systems provide. Cloning into a new object with different traits is perhaps an option.
Inheritance has its place in game dev though. It's just that your hiearchy needs to be designed in the space of what you are actually building: not vehicles, weapons, and monsters but renderers, physics, AI, GUIs, etc. And you want to figure out how that stuff is going to work before you break it up into classes.