5 ms·
I found when I started programming I never used OOP. Then I used it too much. And then recently I use it incredibly sparingly. I think this is most people's exp
by jphoward 6y ago
I found when I started programming I never used OOP. Then I used it too much. And then recently I use it incredibly sparingly. I think this is most people's experience.
However, there are certain situations where I cannot imagine working without OOP.
For example, GUI development. Surely nobody would want to do without having a Textbox, Button, inherit from a general Widget, and have all the methods like .enable(), .click(), and properties like enabled, events like on_click, etc.?
Similarly, a computer game, having an EnemyDemon inherit from Enemy, so that it has .kill(), .damage(), and properties for health, speed etc.?
I'd really like to know how the most anti-OOPers think situations like this should be handled? (I'm not arguing, genuinely interested)
- leontrolski 6y agoIn game development even there seems to be a shift away from OO, to data + functions under the guise of "Data Orientated/Driven Development" - eg: https://www.youtube.com/watch?v=0_Byw9UMn9g https://www.youtube.com/watch?v=0_Byw9UMn9g Edit: ignore me - this person seems to know more what they're talking about - https://news.ycombinator.com/item?id=25933781 https://news.ycombinator.com/item?id=25933781
- setr 6y agoAt least regarding games, ECS is the popular flavor-of-the-month alternative to OOP as a design strategy. The main problem being that games often have a lot of special cases, which break the inheritance hierarchy quite quickly. E.g. Defining a weapon > {sword, wand} hierarchy, with respective properties for melee and casting, and then defining a unique weapon spellsword which is capable of both melee and casting. You could inherit from weapon, and copy & paste sword/wand code, or inherit from sword/wand, and copy & paste the other, but the hierarchy is broken. ECS would rather have you define [melee] and [casting] components, and then define a sword to have [melee], wand to have [casting] and spellsword to have [melee, casting]. So instead of representing the relationships as a tree of inheritance, you represent it as a graph of components (properties). And then you generically process any object with the melee tag, and any object with the casting tag, as needed. And of course then you could trivially go and reach out across the hierarchies and toss [melee] onto your house object and wield your house like a sword -- I don't know why you'd want to do that, but the architecture is flexible enough to do so (perhaps to your detriment). Dwarf Fortress probably has the best example of this: https://github.com/BenLubar/raws/blob/archive/objects/creature_amphibians.txt https://github.com/BenLubar/raws/blob/archive/objects/creatu... That's probably more an example of "metadata-driven" but it's ultimately the same thing -- an entity in the game is defined by its components, and the job of the game engine is to simply drive those components through the simulation. That particular example has its metadata (e.g. aesthetics: [CREATURE_TILE:249][COLOR:2:0:0]), its capabilities (e.g. [AMPHIBIOUS][UNDERSWIM]) and its data (e.g. [PETVALUE:10][BODY_SIZE:0:0:200]). And it even has inheritance :-) [CREATURE:TOAD_MAN] [COPY_TAGS_FROM:TOAD] [APPLY_CREATURE_VARIATION:ANIMAL_PERSON]
- jgwil2 6y agoIn case others are wondering: https://en.wikipedia.org/wiki/Entity_component_system https://en.wikipedia.org/wiki/Entity_component_system
- anaerobicover 6y agoNote for readers who want to search for more: ECS is "Entity Component System" And Eric Lippert has a fantastic series of blog posts where he also discusses this problem: https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/ https://ericlippert.com/2015/04/27/wizards-and-warriors-part...
- setr 6y agoexactly the post I was trying to remember when talking about spellswords :) Those posts are also cool in that defining games as a set of rules that operate on things within it is a really neat mental model -- the program should basically look like a DnD rulebook, with statblocks and all.
- anaerobicover 6y agoYeah, it's definitely among my favorite essays. Btw, your explanation was excellent as well!
- reidjs 6y agoThanks for sharing that Dwarf Fortress file, I never thought about the structure for all those attributes.
- vadansky 6y agoI've been hearing about ECS for a decade so it's definitely more then flavor of the month. However the issue is that unfortunately Unreal/Unity are both OO first.
- setr 6y agoIt's definitely been around but I think Unity's (never-finishing) ECS + Rust gamedev community's focus on it has really spiked its popularity/interest lately. Otherwise pretty much every recommendation/engine is OOP-based, with a few straggling extensions/libraries for ECS here and there. No idea about usage in industry though, but it comes up randomly e.g blizzard: https://www.youtube.com/watch?v=W3aieHjyNvw https://www.youtube.com/watch?v=W3aieHjyNvw
- thepratt 6y agoIf we step into the haskell land of monads having explicit functionality kept in individual monads with their own instances would be one way to segment this type of stuff. Something vaguely written as below would let you run actions where anyone can move or is an enemy, and default implementations can be provided as well. data Demon = { ... } data Action = Dead | KnockedBack | Polymorphed class Character a where health :: Int class (Character a) => Movement a where speed :: Int class (Character a) => Enemy a where kill :: a -> Action damage :: a -> Action instance Character Demon where health = 30 instance Movement Demon where speed = 5 instance Enemy Demon where kill _ = _ damage _ = _ https://soupi.github.io/rfc/pfgames/ https://soupi.github.io/rfc/pfgames/ is a talk going through an experience building a game in a pure fp way with Haskell and how they modelled certain aspects of game dev. Most of the code examples are when you press down in the slides.
- beaconstudios 6y agothe patterns can still be completely the same without OOP - pairing data and functions that operate on said data. enemy.damage() and Enemy::damage(enemy) are functionally and semantically equivalent. But in the latter case (where you separate data and code) you don't need to worry about object assembly/IoC, how to pass a reference to A all the way through the object graph to object B, composition over inheritance becomes the default (at least in my experience using TypeScript interfaces, YMMV with other languages). The benefits of OOP, primarily state encapsulation, stop looking like benefits when it turns out your state boundaries weren't quite right. Of course I'm biased as I went through the same "procedural => OOP => case-by-case" learning curve as the GP. But I ended up spending a lot of time trying to satisfy vague rules when using OOP - with procedural/functional programming with schema'd data, I get to spend a lot more time on what I actually want to do. Not worrying about SRP, SOLID, object assembly, how to fix my object graph now that A needs to know about B, and so on.
- jcelerier 6y ago> how to pass a reference to A all the way through the object graph to object B you just end up replacing the object graph by the call graph, which makes all the business logic much messier as now every function call takes a "context" argument
- beaconstudios 6y agoThat's not been true in my experience - what you do end up with is a global data structure, which is the shared state for all or most non-ephemeral top level concerns. Aside from recursive functions I find call stacks tend to be quite short.
- deleted 6y ago[deleted]
- nickjj 6y ago> Similarly, a computer game, having an EnemyDemon inherit from Enemy, so that it has .kill(), .damage(), and properties for health, speed etc.? I'm not a game developer in the slightest but as a gamer and developer I've often thought about similar things a little bit. In another example, let's say you were playing a game like Diablo II / Path of Exile where you have items that could drop with random properties. Both of those games support the idea of "legacy" items. The basic idea is the developers might have allowed some armor to drop with a range of +150-300% defense in version 1.0 of the game but then in 1.1 decided to nerf the item by reducing its range to +150-200% defense. Instead of going back and modifying all 1.0 versions of the item to fit the new restrictions, the game keeps the old 1.0 item around as its own entity. It has the same visible name to the player but the legacy version has the higher stats. Newer versions of the item that drop will adhere to the new 1.1 stat range. That made me think that they are probably not using a highly normalized + OOP approach to generate items. I have a hunch every item is very denormalized and maybe even exists as its own individual entity with a set of stats associated to it based on whenever it happened to be generated. Sort of like an invoice item in a traditional web app. You wouldn't store a foreign key reference to the price of the item in the invoice because that might change. Instead you would store the price at the time of the transaction. I guess this isn't quite OOP vs not OOP but it sort of maybe is to some degree. I'd be curious if any game devs in the ARPG genre post here. How do you deal with such high amounts of stat variance, legacy attribute persistence, etc.?
- pje 6y ago> I cannot imagine working without OOP ... in GUI development What is React but essentially a (wildly popular) functional GUI framework?
- nicoburns 6y agoReact with hooks offers one decent paradigm for this. I would definitely class it as an area that doesn't yet have a canonical settled solution yet.
- keyle 6y agoI completely agree with you when it comes to UI although the key here is that OOP is syntactic sugar, it shouldn't be the overarching pattern. I think of it as augmenting types, or prototype-based OO. And when it comes to games, nope; entity patterns with composable behaviours added to dumb objects is far more productive that traditional OOP, as you need many objects with slightly different behaviours/abilities/types. Composition over inheritance is key here.
- coldtea 6y ago>However, there are certain situations where I cannot imagine working without OOP. (...) Similarly, a computer game, having an EnemyDemon inherit from Enemy, so that it has .kill(), .damage(), and properties for health, speed etc.? You'd be surprised. This is much cleaner, and more efficient too: https://medium.com/ingeniouslysimple/entities-components-and-systems-89c31464240d https://medium.com/ingeniouslysimple/entities-components-and... more in depth: https://www.dataorienteddesign.com/dodbook/ https://www.dataorienteddesign.com/dodbook/
- leshow 6y agoI used to think this too. And I have to admit OO does lend itself well to GUI and hierarchical structures (IMO probably the only case I think it is actually useful). But that's not to say that there aren't declarative methods that are equally nice to use, Elm has done a lot to popularize this style of coding GUIs. example in Haskell, https://owickstrom.github.io/gi-gtk-declarative/app-simple/ https://owickstrom.github.io/gi-gtk-declarative/app-simple/ or even take a look at yew in Rust which is also elm-style, https://github.com/yewstack/yew/blob/master/examples/counter/src/main.rs https://github.com/yewstack/yew/blob/master/examples/counter...
- fulafel 6y agoThere are a bunch of FP GUI development styles without OO. The Elm style[1], which propagated to a whole family tree of similarly structured system, and similar ways in Reagent based apps in the ClojureScript world. https://guide.elm-lang.org/architecture/ https://guide.elm-lang.org/architecture/
- mistersys 6y agoThe answer is simple: Traits The weakness of OOP structurally stems almost entirely from inheritance, which I think is very poor construct for most complex programs. How should the widget situation be handled? Well, what is a widget? It's hard to define, because it's a poor abstraction. Maybe all your "widgets" should be hide-able, so you implement a `.hide()` and `.show()` method on `Widget`. Oh, and all your widgets are clickable, so let's implement a `.click()` method. Oh wait.. but this widget `some-unclickable-overlay` is not clickable, so let's build a `ClickableWidget` and `ClickableWidget` will extend `Widget`. Boom, you're already on your way to `AbstractBeanFactory`. We got inheritance because it's an easy concept to sell. However, what if we talked about code re-use in terms of traits instead of fixed hierarchies? So, our `some-unclickable-overlay` implements Hideable. Button implements Hideable, Clickable. We have common combination of these traits we'd like to bundle together into a default implementation? Great, create super trait which "inherits" from multiple traits. Rust uses such a system. They don't have classes at all. Once you use a trait system, the whole OOP discussion becomes very obvious IMO. 1. Shared state can be bad, avoid if possible 2. Inheritance is a poor construct, use traits, interfaces, and composition instead. 3. Don't obsess about DRY and build poor abstractions. A poor abstraction is often more costly than some duplicated code. 4. Use classes if they're the best tool in your environment to bundle up some context together and pass it around, otherwise don't