3 ms·
Class inheritance is flawed because it tries to be two things at once: a shared "surface" (public members, polymorphism, etc.) and shared implementation. An abs
by feoren 3y ago
Class inheritance is flawed because it tries to be two things at once: a shared "surface" (public members, polymorphism, etc.) and shared implementation. An abstraction is only a surface -- this could be an interface, a function declaration, or even a data model. It almost never happens that implementation-sharing and surface-sharing completely coincide, and this is why class inheritance is falling out of favor and something I completely avoid (occasionally I will use abstract classes, but I usually regret it later). This is where "favor composition over inheritance" comes from. I'd go so far to say that because they cannot be completely divorced from implementation details, base classes cannot even be called abstractions.
So if "wrong abstractions" includes shoddy base-class shenanigans, then the statement becomes almost tautological. Of course duplication is better than class inheritance -- everything is better than class inheritance. So the real statement there is "class inheritance is actually awful", which is important to understand, but a side point to this debate.
If you don't count class inheritance as abstraction, then the tradeoff between code duplication vs. abstraction becomes much more nuanced, and that's what all this discussion is about. I certainly don't agree that ignoring class inheritance is a signal that the author is amateurish. Many complex codebases have no class inheritance at all.
- jeremyjh 3y agoInheritance is very useful in domains like game engines, where it is very common to have a base object such as "Node" that has some properties that every object in the scene graph must have, which all share the same implementation. For example they should all have a parent property and a collection of children, and ways to modify those properties. They'll also share methods such as "render" which probably must be overridden in every subclass. Its not impossible to solve this with interfaces and composition but those solutions are sub-optimal. An example you might be more familiar with is the DOM of a web browser - every element has some basic properties and methods that all share an implementation.
- yellowapple 3y ago> Its not impossible to solve this with interfaces and composition but those solutions are sub-optimal. The growing popularity of ECS and data-oriented design in game engines suggests otherwise: keeping components separate from entities enables both performance enhancements and separations of concerns that are much more difficult to achieve with the traditional inheritance-based approach. To illustrate a bit: > it is very common to have a base object such as "Node" that has some properties that every object in the scene graph must have, which all share the same implementation. For example they should all have a parent property and a collection of children, and ways to modify those properties. You don't need subclasses for that; you just need a table of entity IDs (where both the things to render and the scene itself are entities) and parent IDs, which you can then recursively walk to get the entities you want to render: WITH RECURSIVE entity_children AS ( SELECT id, parent FROM entities UNION ALL SELECT ec.id, ec.parent FROM entity_children AS ec JOIN entities AS e ON ec.id = e.parent ) INSERT INTO scene_entities (scene, entity) SELECT $scene_entity_id, id FROM entity_children WHERE parent = $scene_entity_id; (Obviously you probably won't actually be running SQL queries in a game engine's rendering loop; this is just to illustrate the logic.) Once you've got that list... > They'll also share methods such as "render" which probably must be overridden in every subclass. You don't need subclasses for that; you just need a table of entity IDs and things to render, which you can then query and send to the GPU: INSERT INTO some_buffer_in_GPU_memory (entity, mesh, texture, position) SELECT se.entity, em.mesh, et.texture, ep.position FROM scene_entities AS se JOIN entity_meshes AS em ON se.entity = em.entity JOIN entity_textures AS et ON se.entity = et.entity JOIN entity_positions AS ep ON se.entity = ep.entity WHERE se.scene = $scene_entity_id; (Again: you probably ain't actually using SQL for this; this is also overly simplified, since most modern game engines use all sorts of other stuff besides a mesh, texture, and position when rendering something. Note also that "em.mesh", "et.texture", and "ep.position" need not be actual meshes/textures/positions, but could instead be indices into buffers already on the GPU.) The key advantage in both of these cases is that the parent/child data and the render data can live where they make the most sense, and can be processed by independently-running systems with minimal contention. This is critical for processing game logic in parallel - something which the game industry is learning the hard way with legacy engines that can't fully exploit multicore hardware.
- feoren 3y agoQuite the opposite: game engines are one of the few places where the sub-optimality and fundamental problems with object inheritance became so overwhelming that people starting abandoning their deeply ingrained CS 101 models of Dog : Animal and invented Entity-Component-System architecture, which at its extreme uses no object inheritance at all and is a deeply "relational" model. Game engines which don't do this were either mostly developed before ECS was invented/popularized (Unreal) or are specifically targeting beginners who have little more than a CS 101 understanding of OO programming (and also following Unreal's lead). DOM elements are a better example, but just because that's how they are done doesn't mean that's how they should be done. Does a <script> element really need a "focus()" method? It has one. Does a <br> element need an "innerHTML" property? It has one. Does a <head> element need an "offsetHeight" property? It has one. If you look at the history of the development of HTML and JavaScript as a shining ideal of software engineering, you're certainly in the minority (this is all before TypeScript, which is a shining ideal of type systems!). The HTMLElement class has 134 properties, most of which make no sense for most elements. It has a long history and a lot of excuses for becoming what it is today, but I would not recommend you follow that lead in your own designs.
- whstl 3y agoNot really. Composition has been the preferred technique for a long time already. A lot of Games and GUIs that use inheritance worked in spite of that inheritance. In more complex object graphs there were always things like override boolean DoNotActuallyRender() in one or two children of the RenderableNode class to account for special behaviour. ECS is just the nail in the coffin of inheritance in game engines. And it's not even new anymore, it has been fashionable for what, almost 15 years now?