Hacker Timesnew | past | comments | ask | show | jobs | submitlogin

"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.



> 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.


A 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!


I've never heard this as an acronym, but I use it in practice! For me, it's an extension of "three or more, use a for" that made sense for all repetition and not just loops.


That’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.


I love this. And I like it's also consistant with the findings of the natural semantic Metalanguage.

NSM would be the minimum element set of concepts shared by all human cultures, which can be used to derive the rest of concepts, and it's heavily based on anthropological field studies.

And it includes the numbers one, two and many, (also few, some & all). So that indicates some special meaningful distinction for us in that jump from two repetitions to start abstracting away.


Or to paraphrase some sayings / one-liners, "a little copying is better than the wrong abstraction".


I've also heard "We Enjoy Typing"


KISS is universal, not only to programming.

But the single responsibility principle and the idea that you should divide your code in components where the lower components should be abstract by the point of view of the higher ones are almost never wrong.

Not by coincidence, those last two are consequences of how people think. While KISS is kind of a physics law.


KISS and YAGNI are like yin and yang for me.


they are yin and yin for me.


Perhaps yeah, I just meant two different sayings that are the same vibe. Yin and yang probably was the wrong choice of term haha


Well, YAGNI does have both yin and yang in it...


Nah, 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.


Is that an example of decorator pattern?


Kinda - 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...


I'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).


> 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).


Another 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.


Even a single object gets you into the "all I wanted was a banana territory. "

I have a gorilla class and a banana method on the class, can I ever use banana without the gorilla? No. The simple act of having a class screws it all up.

Make the banana a pure function, or one that is defined in terms of a polymorphic parameter that it mutates.

  func banana(x: animal){...}

  banana(gorilla);
The way to do it via OOP is base class methods and importing the method to all children via inheritance which is just bad and universally reviled among even practitioners of OOP.

  class Animal() 
  {
      banana()
  }

  class Gorilla() : Animal {
  
  }
Technically there are slight fair advantages and disadvantages to the OOP way when you don't account for inheritance and polymorphism but all that is gone once you do account for them.


I guess the exception would be when you’re modeling a graph.


Hah, 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. 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.


Oh 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.


Funny 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.


This 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


Then 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?


Go 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

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

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!)


Thanks 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.


So 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.


Go 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)".


https://en.m.wikipedia.org/wiki/Structural_type_system

By structural typing. It’s a form of satisfying an interface by composition.


I 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.


My policy is OOP is like Heroin. Don't even start.


Never write GUI code then.


I try really hard not to. So far it's worked!


MVU doesn't need OOP though.


Yeah, assuming you are using something like C.


Are you by chance thinking of MVC?

MVU is more towards functional rather than imperative programming.


Not when using an OOP language like JavaScript, you are by definition using OOP constructs exposed as language fundamental types.


Yeah 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.


I 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.


Actually there is NuclearJS a Flux (React) implementation in JS designed around immutability, FP and also reactive programming. As anything pure FP, it's ridiculously difficult/awkward to get started but it really shines in some areas. Namely complex UIs, state transitions and interrelated data "objects". The first 2 can be solved with proper OOP (it's obviously not nice but it solves the problem), but I've never seen a framework ironing out the nastiness of relational data so well. The idea is to combine multiple data objects using lambdas. It's incredibly elegant and so general concept is supposed to be the "Functional Lens".

Unfortunately it's discontinued, probably rightly because it's definitely not for teamwork. But really, it doesn't depend on anything OOP in the strict sense.


You'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_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

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/

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.


You mention React and Elm, which - as I said previously - both rely on other people's actual GUI work (the various browsers' rendering engines + DOM, the various platform-specific UI SDKs, etc) to actually put anything on the screen.

I can't speak to ReasonML, as I have not used it, but my impression also is that it takes advantage of other people's GUI libraries considering that I've heard Revery compared to Flutter (and Flutter outsources its rendering to Skia as well as relies on rather imperative RenderObjects beneath the functional reactive layers). Please correct me if I'm wrong.

I would be very interested indeed to see a GUI framework built from the actual ground level up in an entirely functional reactive manner. Do you know of any?


Ah so ReasonML doesn't transpile to JavaScript, an OOP language?

How does it run on the browser?


[flagged]


[flagged]


[flagged]


React is a remarkably well-focused library that can work just fine without DOM, itself having no relationship with or dependency on DOM or the browser whatsoever.

If you want to render into DOM, you’ll need to use another library, ReactDOM (notably not a dependency of React).

However, rendering to DOM is not the only way to use React. Ink[0], for example, allows to create command-line program interfaces out of React functional components and JSX. It depends on React, but not ReactDOM.

Whether React internals rely on OOP and to what extent I don’t know, but I can attest that after embracing functional components in a somewhat complex app I’m working on I haven’t written a single class.

[0] http://term.ink/


What is Array.prototype.map()?


You don’t have to use map() if you don’t like it; and you’re free to pick a functional implementation from Ramda or somewhere else and use that instead, React really couldn’t care less.


What is typeof(function)?


Could you try writing a few sentences instead of three words to make a proper counterpoint? My point was that React core can be used (and sometimes forces you to use itself) in a functional manner without DOM or the browser, counter to what you have claimed; the ball’s in your court.


"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.


This 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?


Can 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).


Object programming is MUCH better than object oriented IME.




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

Search: