6 ms·
There is in fact associated behavior; it is just moved off the object in an effort to avoid the after-effects of many languages which implement "OOP". Behavior
by trashburger 3y ago
There is in fact associated behavior; it is just moved off the object in an effort to avoid the after-effects of many languages which implement "OOP". Behavior doesn't have to be part of the object directly, in fact I support your idea that objects can be inert, but behavior inheritance is still an extremely useful concept even within the scenario you propose.
As for your specific example, you don't have to add the behavior of actually playing the sound or animating to your `sword` or `player`; I never suggested that the objects themselves having to perform effects being an inherent part of object programming. Maybe you can send `player wieldedWeapon attackSprite` (or `attackMesh`, I'm fond of 2D platformers better :) and then the scene can ask the animation to render itself, same with `player target hurtSound` and `player wieldedWeapon attackSound` and then the sound object can be asked to play itself with a given `audioContext`. Here, we're being as declarative as can be and still giving the objects the freedom to behave however they want within the bounds of the contract they adhere to.
So an object programming style does not, in fact, preclude you from making "POD objects"; it only says that the behavior of an object should be exposed through the object itself, rather than reflectively deciding on what the object should do from the outside.
- sirwhinesalot 3y agoNo, I disagree, because the right behavior is not determined by a single object but by a combination of factors. For example when the sword strikes an enemy all of the following need to happen: - The check that the "enemy" is even a valid target (who decides that? The player? What if certain enemies are immune to slashing attacks? The sword then? What about the enemy making the decision? What if it depends on the armor the enemy is wearing, does the armor decide?) - The calculation of the position of where the sword impacted the enemy (who calculates that? it depends on the animation calculation results, the size and shape of the sword and of the enemy, etc.) - The sound that plays depends on the type of equipment the enemy has (leather armor vs steel armor) and on the weapon of the player (a sword or a hammer would have different sounds). - The actual playback of the sound depends on all of the decisions above (the animation, the collision, the materials involved in the collision). At no point, in any of this, are there messages between these objects. The entities that have to make these calculations depend on multiple data sources, none of which belong to them, that is, the behavior is not associated with the data. An animation doesn't render itself (rendering requires a rendering context, mesh data, texture data, shaders, etc., who owns them?). A sound doesn't play itself. Collisions don't calculate themselves. Inert objects don't inherit behavior because they have no behavior, systems do. And systems are effectively "manager" objects (AnimationManager, AudioManager, CollisionManager, etc.), widely considered a bad practice in OO circles. Systems should indeed support behavior inheritance (I might want multiple different implementations of an AudioManager for different targets or with/without surround sound support, etc.) and since their internal data is hidden (i.e. operating system resources), they are very much objects and working with them is OO programming. But the rest of the system is not OO, it's very much plain old data being computed upon by plain old functions, and that has nothing to do with OO nor does it need any kind of inheritance. Even in Smalltalk you see this issue happening. Ints and Doubles respond to messages to implement arithmetic, but for example reading an integer from a stream is a method of the Number class that takes an integer radix and a Stream as input. Why is it not a method of the integer radix or of the stream? Why can't different streams decide how to parse themselves into integers? Because the whole concept of associating behavior here is silly. readFrom is a function.
- cageface 3y agoThanks this is a great example of the kind of problem you run into with OO designs. State is a graph and you very often need to transform a whole subgraph of it in one operation. Traditional OO makes this very difficult.
- arrow7000 3y agoExcellently said. You've put into words things I've felt for a long time but never got round to articulating
- evntdrvn 3y agoSee also https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/ https://ericlippert.com/2015/04/27/wizards-and-warriors-part...
- sirwhinesalot 3y agoI cannot upvote that series of blog posts enough. It is exactly what I'm talking about.
- trashburger 3y agoLet's go point by point. > No, I disagree, because the right behavior is not determined by a single object but by a combination of factors Sure, and you can ask one or more of the objects involved about all of those factors! > The check that the "enemy" is even a valid target (who decides that? The player? What if certain enemies are immune to slashing attacks? The sword then? What about the enemy making the decision? What if it depends on the armor the enemy is wearing, does the armor decide?) Answer: All of them! Let's walk through it step-by-step: - The player wants to attack the entity it's pointing at: `aPlayer attackEntityCurrentlyPointedAtIn: world.` - In order to do this, the player asks the world what it's pointing at first: `entity: world entityPointedAtBy: self.` If nothing is returned, then the player would simply play the shoot animation of the wielded weapon. - If an entity is returned, the player asks the enemy to be attacked by itself: `entity getAttackedBy: self`. - The enemy checks whether it's the right kind of entity first (attacking a prop might do nothing, for instance). Afterwards, it asks the source entity about the various factors you talked about (what kind of attack the source's currently wielded weapon would produce, what the armor is, etc.) and responds whether it could be attacked and how much damage was produced. Maybe the enemy could even deal damage back! `source dealDamage: 123 Source: damageSources reflection.` > The calculation of the position of where the sword impacted the enemy (who calculates that? it depends on the animation calculation results, the size and shape of the sword and of the enemy, etc.) We already would need the animation context in order to make sense of the animation that would be produced by the attack at this point, so we can just thread it through our messages and ask where on the target entity the strike would happen. Because we would be asking the wielded weapon about the details of what kind of animation it would produce, it can add in any details and modifiers you'd like. > The sound that plays depends on the type of equipment the enemy has (leather armor vs steel armor) and on the weapon of the player (a sword or a hammer would have different sounds). > The actual playback of the sound depends on all of the decisions above (the animation, the collision, the materials involved in the collision). Let's just ask the attack source about what kind of material it is, and then what it would produce if it hit us, and then ask the sound to play: weaponMaterial: source wieldedWeapon material. "Assuming you need this level of detail" ownMaterialAtStrikePoint: strikeSurface material. weaponMaterial soundForStrikingAgainst: ownMaterialAtStrikePoint ; play: audioContext. > At no point, in any of this, are there messages between these objects. I think I was able to prove this wrong. ;) > The entities that have to make these calculations depend on multiple data sources, none of which belong to them, that is, the behavior is not associated with the data. This isn't how the real world works though. Every action has a fundamental source and a target. Being able to model how different things interact in our system in such a natural manner like message passing can make everything so much clearer. > An animation doesn't render itself (rendering requires a rendering context, mesh data, texture data, shaders, etc., who owns them?). A sound doesn't play itself. Collisions don't calculate themselves. But they do! They are the best at rendering, playing and calculating themselves, because they're the information expert: they hold all the details. The code that sends the `play:` message need not know anything about whether the sound is a waveform or Opus; it need not know whether the sound object is actually a `pitchChange` object that modifies the original sound effect. All it needs to know is that the object can be `play:`ed. > Inert objects don't inherit behavior because they have no behavior, systems do. A system is simply a hierarchy of objects, as I detailed above, so they absolutely can have behavior. > Even in Smalltalk you see this issue happening. Ints and Doubles respond to messages to implement arithmetic, but for example reading an integer from a stream is a method of the Number class that takes an integer radix and a Stream as input. Why is it not a method of the integer radix or of the stream? Why can't different streams decide how to parse themselves into integers? Because the whole concept of associating behavior here is silly. Actually, it makes much more sense than a `stream readInt` ever could. Why does a stream know how to read an integer? Why does it need to know how an integer looks, and how to create one? It is much more natural to ask an integer to take in a stream and create its own representation, IMO.