Composition and inheritance
The key reason for composition over inheritance is decoupling, or rather, modules like building blocks, with state handed in from outside. Static methods are enough most of the time, but once in a while what you want is a composition class where a bunch of methods share the same state. But then why don’t I go all the way to DI and let the state be injected from outside? Put more precisely, I only need a class when all the composed methods have to share the same single piece of state, and even that state still comes in from outside the class. The difference is DI on a method versus DI on a class. For the normal case, DI on the method is enough.
Composition also keeps raising the question of when to use it. Composition covers the difference between “has a” and “is a”. A class is literally “a kind of thing”, meaning they’re one species, sharing structurally fixed (as said above), predefined parent state (properties) or methods. A subclass can write its own, but only where the parent allows it, and the subclass’s scope and boundaries are limited by the parent. What’s different from an interface is that an interface constrains structure too, but it focuses on abstracting the structure itself, while a parent class is an abstraction one level lower down, and besides structure it also has concrete behavior, methods with real bodies. That concreteness is the basis for the Liskov Substitution Principle (LSP). Any subclass can stand in for the parent, because it fully contains (inherits) everything the parent has. The extra parts just don’t get used.
But standard LSP isn’t a hard constraint. Say the lower layer depends on an “overridable” method of the parent and the subclass overrides it completely, that blows up. This is also why abusing inheritance leads to coupling. Inheritance that doesn’t satisfy LSP shouldn’t exist in the first place, or rather, the parent should state that its overridable methods are for “extension”, and those extension parts can be a contract in themselves, what they’re called, what goes in and out, with only the logic inside left for you to write however you like. Looked at this way, you’ll find that even with extension a well-designed parent can keep LSP working, because the structure (input-output) is fixed by types, and normally, even with extension, nothing goes wrong when you use it. From the start you should make sure a subclass can’t break the core functionality when it inherits, and that is the skeleton that keeps LSP holding. Even so, this is a structural constraint. If the subclass just messes around, so the method errors out, returns null (the return type system doesn’t cover that by default), or pulls off the expected side effects, it still breaks. You can’t cover everything. Once you open an extension point, all sorts of monsters can come out. Maximizing LSP is doable, and that’s exactly what abused inheritance can’t do.
If I need to focus on structure and on concrete implementations of that structure, and I want whoever depends on it later to “inherit” (literally) that content, then a base class with inheritance is the standard move, with nothing redundant about it. I can’t let a human be a Frankenstein, patched together out of a pile of unrelated “blocks”. Even if it runs in the end, it’ll be as confusing as Frankenstein’s monster. It moves, as I said above, but our sense of a human is that a human has its own inherent state, and that state has to be inherited rather than patched together. A human has to be an “is a”, so it isn’t the standard move at all, it’s an “anti-pattern”.
But in some cases composition is what we need, blocks, decoupled modules. It’s aimed at “has a”. If I only want a reusable feature, like Lego that snaps together anywhere, and it needs an interface but doesn’t depend on any fixed state, composition couldn’t fit better. Whether you build a house or a helicopter out of it doesn’t interest me. As long as you can snap it together, you can use it.
Put that way, composition can also be used together with inheritance, say in a large utility module. Even Lego comes in types. You’re a brick, I’m a minifig, minifigs snap together freely, but a minifig (generally) doesn’t connect to a brick. At that point a family of “has a” features has to build itself along the “is a” pattern. The whole thing is still a “has a” module, but it’s its own line.
Translation note. I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: 組合和繼承.