9 ms·
"Tool" is still a class, with perhaps very generic polymorphic methods (e.g. do_default_action() ). The problem is not polymorphism per se, but rather about hav
by truetraveller 5y ago
"Tool" is still a class, with perhaps very generic polymorphic methods (e.g. do_default_action() ). The problem is not polymorphism per se, but rather about having a deep class hierarchy aka lasagna code.
My policy: OOP is like salt. Use little and that's great. I only allow a single inheritance layer, and ideally no inheritance at all.
- AstralStorm 5y agoFunny thing, it's just the object model in play that causes the problem. Something more similar to Smalltalk (or Objective-C) works pretty well for things this dynamical, though you do need to factor out the properties and messages reasonably well still. The trouble is C++ and similar object models poor support for composition and implicitly penalizing uniform object structure. (E.g. call multiple base methods in a subclass - pain ensues. Add virtual calls to the mix, it gets really iffy.) Even Python and Ruby with explicit mixins but the old model get hairy.
- mountainriver 5y agoThis is one of the things I like about Go. Just structs with methods. You can embed them but it seems to hedge against deep nesting and creates simple code
- blacktriangle 5y agoThen how would you model composing behaviors in Go. Say for example I have a representation of Food and a representation of Animal. I then want an ability that will "animate" things, so I can animate Food to give it the behaviors of Animal. I'm not being critical either, I'm seriously curious how somebody would implement this behavior in Go. Like you say, just struct with methods is a very appealing mental model since you have fewer moving parts, but how does it deal with a scenario like this which is very common in game dev?
- dilap 5y agoGo has interfaces, but not inheritence. I'm slightly confused about what you're trying to describe, but maybe it's something like this? https://play.golang.org/p/smPyWvBvWWp https://play.golang.org/p/smPyWvBvWWp We can make both Food and Animal statisfy the "Animateable" interface. Go also has a syntactic shortcut which is similar to inheritence, but more explicit. If you include a field in a struct without any name, it is considered "embedded", and you can call methods on the field directly, without referencing its name, which ends up looking the same, at the call site, as inheritence. https://play.golang.org/p/cIMqQqE0eVc https://play.golang.org/p/cIMqQqE0eVc But everything is still explicitly laid out in the struct -- it's just a simple struct, no inheritence shenanigans. I think interheritence is a mistake, but interfaces are the bees-knees. Even in C# (which is my main language these days, 'cuz that's what you use if you're making a game in unity), I just use interfaces, not implementation inheritence. (And even interfaces I don't use much -- but when you need it, you need it!)
- blacktriangle 5y agoThanks very much for the reply. Here's more of a clarifying example: The idea I was going with Animateable was not animation (poor wording on my part), but effictively giving the properties of a creature to a thing, ie magically animating it. So I have an ability in-game that lets me turn anything into a creature, thus merging the Creature interface with whatever other interface this has. From what I'm getting from you, that means every single thing in the game must implement the Animateable interface. Now lets do another one, I want a Metalic property. Say these are things that when hit by lightning will give off lightning damage. Then there's a curse called Midas that will let me turn anything metallic. So I have a Wooden Hammer object which I midas curse mixing in metalic, then a wizard animates it so that it has the properties of a creature, and I have a creature that when I hit with lightning gives off sparks that I can also use to hammer nails. Point being, in this world you're basically requiring every single thing to implement every single interface which sounds like a nightmare, where with more of an ECS style system, you just merge in the new behavior and call it a day.
- dilap 5y agoSo caveat, I've never actually used an ECS system, but I've read about the pattern, and I would say it definitely seems better suited to the kind of situation you're describing than interfaces (or inheritence). (I would say interfaces/inheritence are useful for simple high level abstraction of basic concepts with multiple implementations (here's an interface for a compression algorithm, or a RNG, or whatever), not so useful for trying to model complex game-world relationships. And to me, the useful part is the interface, whereas inheritence mixes in two unrelated concepts: implemementation-sharing and interface.) But you don't need either of those for ECS, right? If I understand it correctly, ECS at its core is basically an optimization of the struct-of-arrays datastructure. E.g., something like this is a super-basic ECS: struct World { Comp1[] Comp1 bool[] HasComp1 Comp2[] Comp2 bool[] HasComp2 ... int NumEntities // all arrays have this length } I'm a big fan in general of struct-of-arrays vs collection-disparate-structs-with-pointers. Besides being way faster, it tends to make code clearer, too. ECS seems like a more efficient and convenient evolution of that pattern. At least from what I've read. No real experience. Take with 13 grains of salt. :-) P.S. If you feel like it, I'd be happy to explore some concrete code (you first!). It's always very interesting to me to try to explore all possible ways to express some concept in code.
- throwaway316943 5y agoI am not a Go programmer but I would assume that each behaviour of Animal would be a struct with one or more methods. Those behaviours are added to the agent to make it an animal. Composition. If you want your Food item to walk like a duck and quack like a duck you just add Walk and Quack to it. Presumably some other system would take care of identifying those behaviours and calling them as needed.
- dgb23 5y agohttps://en.m.wikipedia.org/wiki/Structural_type_system https://en.m.wikipedia.org/wiki/Structural_type_system By structural typing. It’s a form of satisfying an interface by composition.
- mgsouth 5y agoGo uses duck typing. "Interfaces," such as Food or Animal, declare sets of functions that must be implemented in order to qualify as an object of that type. So a "Food" thing might need to implement "beInjested()" and "spoil()", and Animal "run()" and "die()". If, for type "Chicken", there are "beInjested(Chicken)", "spoil(Chicken)", "run(Chicken)", and "die(Chicken)", then a Chicken can be used anywhere a Food or Animal is wanted. edit: You cannot, however, explicitly declare that a "Chicken" is supposed to be a Food and/or an Animal. And you can't directly inherit implementations; to make Chicken directly use a generic function you need to do something like implement "baseSpoil(Food)", and then have the body of "spoil(Chicken)" explicitly call "baseSpoil(self)". As mentioned in another reply, there is a shortcut. You can declare that the Chicken structure includes (anonymous) Food and Animal fields. Then, if you call "spoil(myChicken)", Go will automatically replace it with "spoil(myChicken.Food)".
- sbacic 5y ago> My policy: OOP is like salt. Use little and that's great. I only allow a single inheritance layer, and ideally no inheritance at all. That's been my observation as well - there's no free lunch and DRY isn't free either, certainly not if you use inheritance chains to achieve it. I'm starting to think that the only programming axiom that will survive in the end is KISS.
- junon 5y agoKISS and YAGNI are like yin and yang for me.
- suzzer99 5y agoA few weeks ago some commenter here said they ascribe to WET - Write Everything Twice. Basically worry about abstraction when you get to number three. And what an acronym given the context!
- hprotagonist 5y agoI've also heard "We Enjoy Typing"
- agency 5y agoThat’s great, and jives with my experience. You probably don’t know the problem well enough to come up with the right abstraction until at least example #3.
- hutzlibu 5y ago"I only allow a single inheritance layer" I would agree to that as a goal, but not as a dogma. It depends what you build, I guess.
- deltaonefour 5y agoMy policy is OOP is like Heroin. Don't even start.
- pjmlp 5y agoNever write GUI code then.
- deltaonefour 5y agoYeah that's the one downside of not doing OOP. OOP is the only way to do GUI stuff. Nobody on the face of the earth has ever used Functional Reactive Programming. It's a made up concept. In fact it's also definitely not one of the concepts that inspired the most popular pattern in React.
- filleduchaos 5y agoI am unaware of a single functional reactive UI library or framework that does not rely on other people's, most often object-oriented (or even *gasp* procedural) libraries to actually put anything on the screen. Of course it's relatively easy to build shiny clean wrappers around the dirty work that others have had to do on your behalf.
- deltaonefour 5y agoYou're right you are unaware. This is not an insult and I'm not trying to offend you. This is a factual statement. You truly are unaware as you stated yourself, and I can prove it to you. First off FRP is actually a term. Functional Reactive Programming. It is 100% a functional paradigm that gasp by DEFINITION cannot be procedural. So you truly didn't know what you were talking about here: https://en.wikipedia.org/wiki/Functional_reactive_programming https://en.wikipedia.org/wiki/Functional_reactive_programmin... Second there are many languages/frameworks that use this paradigm. React partially uses this paradigm. But I will list two popular ones that strictly follow it... just note that there are more. Much more. Take a look at elm. Elm is a fully functional UI library and language that is 100% pure, functional and has no OOP. Believe it or not ELM is not some toy, it is production ready and actually measurably faster than react. This language is what popularized the FRP pattern which is partially used by React today. See here: https://elm-lang.org/examples/mario https://elm-lang.org/examples/mario Note the load time and latency on the mario game. React doing the same thing will be slower. Another language is ReasonML. ReasonML is essentially a language that compiles to the browser and has one to one correspondence with another functional language... OCaml. ReasonML is Created by the creator of React, Jordan Walke. Coupled with React as framework, ReasonML is essentially the ideal GUI paradigm that Jordan would recommend everyone to use in the ideal world. However due to the fact that everyone is use to javascript, Jordan instead as a first step, ported FRP concepts over to a language called JSX (essentially JavaScript mixed with html) and is slowly nudging the world in the direction of GUI programming using the functional style: https://reasonml.github.io/ https://reasonml.github.io/ Make no mistake the creator, of the most popular GUI framework in the world is a supporter of the pure functional paradigm, and he is heavily and successfully pushing the functional style programming of GUIs into the mainstream. OOPs being the dominant paradigm for GUIs has, in the past five years, become a false statement. You're right on the wrapper part though. Assembly language is essentially a procedural language so every functional thing that exists on top of it, is a wrapper. But I mean this is a pointless observation.
- blacktriangle 5y agoAnother good rule for OOP: Objects are fine, object graphs are a killer. Graphs are what get you into "all I wanted was a banana" territory. Objects should either be atomic, agrigated by simpler structures like maps and arrays, or if you absolutely must have objects pointing to objects, be sure that they form a tree and not a graph such that each object need only know about the things below them which can be encapsulated at a single point.
- dgb23 5y agoI guess the exception would be when you’re modeling a graph.
- blacktriangle 5y agoHah, that's actually a good point. You still don't necessarily need to model the graph at the object level though. You could model your graph as a set of pairs, ie edges. If performance was more of a concern you could do it as a map where each object was a key and the values were a list of the connected objects. You could also separate out the value from the graph behavior and have a GraphNode object that wraps each value object. The bigger point though is when you have networks of objects that are relying on each other to perform functions, that's where the problem is, and where you start to rely on mocks and stubs for testing, which really should be considered a smell in its own right. Your better off making the interface between objects values rather than having hard references between objects as described in this talk: https://www.destroyallsoftware.com/talks/boundaries https://www.destroyallsoftware.com/talks/boundaries. Once the interfaces between your objects are values instead of references you're free to connect your objects together in any way you need and are not stuck with the fixed graph as originally designed.
- dgb23 5y agoOh yes that is certainly sane. > Once the interfaces between your objects are values instead of references you're free to connect your objects together in any way you need and are not stuck with the fixed graph as originally designed. Absolutely, same can be said about relational data representation for example.
- lmm 5y agoNah, the problem is OOP polymorphism that conflates too many separate things. If you have a language that can actually express "implements this interface", "has this member", and "delegates this interface to this member", then you don't need traditional "extends" inheritance at all; sometimes you want to do the exact equivalent (and you can), but most of the time you want to do something narrower.
- jmartrican 5y agoIs that an example of decorator pattern?
- filleduchaos 5y agoI'd say that that's an example of structural or duck typing (as opposed to nominal typing). In my experience it is nigh impossible to design-pattern your way out of a language simply being unable to express what you need it to express without incurring a severe performance penalty (anathema for game development) or resorting to code generation (which is itself prone to ending up as a Gordian knot).
- lmm 5y ago> I'd say that that's an example of structural or duck typing (as opposed to nominal typing). No it's not. I'm talking about having nominal typing but easy support for delegation (e.g. look at how Kotlin does it).
- lmm 5y agoKinda - an instance of an OO subclass is a decorator around an instance of its superclass, so if you're using a language which has a good way of expressing decoration then that's usually better than using inheritance. But as the other reply said, patterns are a language smell - the language needs to support that style and make it idiomatic. Otherwise, if you give people the choice between doing it right but verbosely and doing it wrong but more quickly, guess which one they'll choose. https://www.haskellforall.com/2016/04/worst-practices-should-be-hard.html https://www.haskellforall.com/2016/04/worst-practices-should...
- Akronymus 5y agoObject programming is MUCH better than object oriented IME.
- SkyBelow 5y agoThis is something I've struggled with in the past as well. I have a player, NPCs, enemies, and wild animals. Should they have any class hierarchy at all? If my code matches the game world, what happens when I was to add golems and let mages turn castle's into entities that act just like an NPC would? Should my class design be so tightly coupled with the elements of my world? I don't think so but what alternative works best?
- markus_zhang 5y agoCan you create a base class for all living things and derive from there? The base class owns a bunch of common components such as HP, Animation, Sound, etc, and each subclass has components that are specific for itself (for example, you cannot "Use" a player, but should be able to "Use" a wild animal, so here goes a component for that action).