Hacker News new | past | comments | ask | show | jobs | submit login

Because the problem in OO is if you have any kind of cross-cutting concern, then it collapses totally.

For example, I have a Wizard object. My wizard has a wand, we store the wand object on our Wizard object. Simple. But then my wizard casts a spell, and does damage to a goblin. Do we put the cast method on the wand, the wizard? There is no real reason to pick one over the other (this problem comes up a lot with game development, which is why this pattern is more common there...Spring is another example, aspect-oriented programming/dependency injection works from similar principles). It is far easier to separate that out totally and have a pure, reusable cast function that takes the wizard, goblin, and weapon.

Another aspect of this problem (which Rust, as an example, makes clear) is that you introduce runtime bugs or hurt performance when you start carrying around a lot of references everywhere. Once you start to think about what actually needs a reference to another object (in Rust, this is limited by the borrow checker) then you realise why OOP doesn't work in some cases.

OO doesn't perform validation, your code performs validation on the data. You can write a separate schema, you can write one schema, but the problem is that OOP tries to fit a round peg in a square hole with some applications.

Very generally, it is harder to make mistakes if you use something like data-oriented. If you have a lot of code with calculations or interactions, it is very pure, easy to test, and fits well with how people think about those elements (one area I have found is financial applications, I actually worked this out and then found out data-oriented program existed when building financial-related stuff). In these cases, introducing OOP means state changing in unpredictable ways (and then someone comes into the project, doesn't understand the abstraction, calls a method that is named erroneously and it all goes wrong).




You have a spell object


>But then my wizard casts a spell, and does damage to a goblin. Do we put the cast method on the wand, the wizard? There is no real reason to pick one over the other

You did pick: "My wizard casts a spell". The cast method goes on the wizard.


Unless cast is an ability unlocked by the wand, then you'd only have "use main/offhand" on the person, and the cast would be on the wand.

It's really not as cut and dry as you said.


Another way to phrase it is to note that the question is answered by the question of what happens when the wand is dropped and the goblin picks up the wand. Can it cast the spell? Then it belongs to the wand. If not then it belongs to the wizard.

In this case it just mean you have to match your game semantics to the decision of where to put the ability. That doesn't mean it would be a good idea to perform fighting by simple method calls on the objects though. It is very likely one would want to extract the abilities granted by skills and object to a separate entity that change seldom enough.

That doesn't mean I see what problems a schema solves here. That mostly seems like unecessary indirection..

Also note I don't try to refute the problem in general, just this specific example. Maths have lots of examples that doesn't have the semantic baggage that makes the question solvable.


You haven't convinced me with that at all, honestly.

You can also have the spell on the wand and require n mana to be put into it for activation (argument), now warrior type goblins won't be able to use it either.

There is no "correct way" with OOP: There are as many ways as you've got the energy to think about and all of them are leaky somewhere. You can still write maintainable code with it, but making it sound clear and obvious where what should go is just wrong. It heavily depends on a multitude of factors and can get nauseatingly complex at times


This ! Each answer here as to where to put it is different or breaks down in different scenarios.

While none of the OOP solutions are outright wrong, neither one are "obviously right".


Multiple dispatch solves that question. I wish it was better known. Dispatch is on both wizard (or generally, user) and wand (or generally, item used)


Right but now it’s also raining and a full moon and the goblin is a werewolf and wands also have AOE spells that hit multiple opponents, except when those opponents are blocking…

Sure, the code kinda sucks either way, but the data oriented approach works exponentially better as the object interactions become more complicated. A “cast” function called as part of the event loop can look up all the game state in the state DB as it needs to. wand.cast(…) is a lot more brittle, ESPECIALLY once one wants to start reusing some of the code in sword.swing(), etc.


> wand.cast(…) is a lot more brittle

That's not how it works. Multiple dispatch looks like a normal procedure call, it would look like

    cast(item, user)
or if you need to know it was raining

    cast(item, user, worldstate)


…and thus code is no longer sending _a_ message to _an_ object but using argument types to pick which function/procedure to call. The “cast” function is no longer coupled with any single class, so in effect this problem with OOP has been fixed by not using OOP concepts to solve the problem.

Or, as I sometimes think of it, it can be an OOP design if multiple dispatch is actually a set of messages supported by an implicit, singleton, and usually hidden, “multiple dispatch“ object that handles the dispatch logic. You don’t have to write “dispatcher.cast(a, b, c)” but that is because the language provides the syntactic sugar (and often, an efficient implementation).


> …and thus code is no longer sending _a_ message to _an_ object but using argument types to pick which function/procedure to call

I dunno. From https://en.wikipedia.org/wiki/Multiple_dispatch

"Multiple dispatch or multimethods is a feature of some programming languages in which a function or method can be dynamically dispatched based on the run-time (dynamic) type or, in the more general case, some other attribute of more than one of its arguments.[1] This is a generalization of single-dispatch polymorphism [...]"

It's just a nicer OOP to me. *shrug* But thanks.


>Right but now it’s also raining and a full moon and the goblin is a werewolf and wands also have AOE spells that hit multiple opponents, except when those opponents are blocking…

>the data oriented approach works exponentially better as the object interactions become more complicated

Maybe it's just me, but I still don't see the difference. To use your example, you'd have a function like:

cast(caster, targets, weather, time, location)

or you can create an object and call it something like Spell and do something like:

class Spell

   def initialize(caster, targets, weather, time, location)
     ...
   end

   def valid_target?
     ...
   end

   def valid_caster?
     ...
   end 
end

Perhaps it's just my mental conception of things. My mental model of a class is a data structure plus a bunch of functions that implicitly take the data structure as a parameter. I realize that there can be more to it than that but it works for the purposes of this discussion.

Ultimately it's a code organization question either way. The original question is what class does it go in? Changing it to data structure + functions just changes the question to what module/file does the cast function go in? Maybe that's an easier question to answer but I guess I just don't see it.


> My mental model of a class is a data structure plus a bunch of functions that implicitly take the data structure as a parameter.

That's not OO. A class without instantiation is just namespacing.


> Right but now it’s also raining and a full moon and the goblin is a werewolf and wands also have AOE spells that hit multiple opponents, except when those opponents are blocking

What exactly does this change? In a well-written program you should be able to write code that adapts to context so that you don't have to pass absolutely everything.

> Sure, the code kinda sucks either way, but the data oriented approach works exponentially better as the object interactions become more complicated

Maybe what you want is actually https://mitpress.mit.edu/books/software-design-flexibility.


Because the function of the spell is to modify the state of the wizard, the wand, and the goblin...that isn't obvious or correct. It can go on the wizard, it can go on the wand but the problem is that the solution is very brittle (because you will likely end up with inheritance as you add other objects). Again, the key point is that in OOP these cross-cutting concerns will likely end up with a design that is complex/arbitrary/unperformant.

This is a good tutorial on this topic - https://ericlippert.com/2015/04/27/wizards-and-warriors-part...




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: