Hello everyone!
Being a knowledgeable programmer (not all-knowing though, don’t get me wrong), I usually don’t have a question, at least not one I can’t Google out in minutes or seconds. (I’ve been programming for decades is all I’m saying so we can go as deep as you’d like in trying to help me.)
However, this time I figured out I should ask for some advice or at least for opinions on one of the code design problems I’ve been having for ages, that don’t seem to be googleable (or acquired from my professional background). So I’m basically curious whether this is just an ugly symptom of the OO form factor in general, or if I’m being silly and there is a decent enough workaround that I’m somehow unable to see.
To understand my problem, let me describe the situation a little more generally. It’s a common pattern, I’m sure plenty of you had this going on before, and numerous times. Namely, you have a very simple hierarchy involving some parent object (I’m talking about dry OO here, not gameobjects) that can own strictly-typed compatible child objects. You feed the parent with some general properties, and instruct it to instantiate its children according to some pattern or rules, and that’s it!
If it helps, this could be as simple as a tilemap, as an example, with children being individual tiles, where each tile has some sort of a unique identifier (in this case it’s their coordinates), and you obviously set the global grid size in the parent, and let it predictably populate the model etc.
Now imagine you’d like to be able to interrogate the individual tiles, and ask them about their size or some other general property, right? It’s a silly example, but bear with me. Because such data is collective and immutable anyway, it doesn’t need to be mindlessly repeated per instance, so a tile would have to be able to internally communicate with its parent to get the result. I hope this paints a clear picture of the set up.
Therefore, if we follow this line of thoughts, the children are dumb, at least when it comes to any information that is clearly above them, while their parent acts as some sort of a representative. It’s their guardian, right? lol And okay, if it’s critical enough and there aren’t that many children, one could obviously pass the reference to their parent during instantiation, thus enabling this internal protocol to take place. A child would do public Vector2 Size => _myParent.Size;
and we’re done.
But now we come to the actual problem. What if I didn’t want to bloat the child with extraneous 4 (or in fact 8 bytes which is a lot) per instance, which is the weight of this completely redundant pointer sitting dead in memory. What if we had hundreds of thousands of children, but only a couple of data points that should be communicated through this, and likely not that frequently, as it’s there mostly for the sake of having a complete and well-intended API?
Yes, I hear you, they could or should be referring to their parent through a static pointer, but that’s not a particularly robust solution because what if we had several different parents with different global parameters and different sets of children? I want to instantiate the entire model several times, so the question is how do you tie up individual tiles with their corresponding parents without storing that reference per instance.
I hope my explanation is sufficiently clear, and that you can see there is a hard trade off about this because you have to pick whether a child knows any extended stuff, and if it does, it becomes much more memory-heavy in bulk, which makes sense really only if it’s supposed to share loads of data, not just couple of things at most. And if it doesn’t know stuff, it either a) has to ask its parent about it, which might be a problematic design (i.e. identifying a child could be an algorithmic problem, and not just O(1), say the parent was keeping track of them in a list, and it’s not just size, but something more exotic instead, a neighbor lookup or something along these lines), or b) simply lacks any extended information, has a really light memory footprint and a straightforward interface, but the design requires you to always interrogate the parent instead, which is sometimes a clear case of a misplaced responsibility.
Let me elaborate this example further: Why would a grid have to provide an answer about some particular tile’s area? Well, because the tile doesn’t know its own size, that’s why, so it cannot extrapolate anything. Dumb as a brick. And if we wanted to move this responsibility to the tile instead, we’d have to repeat a pointer (or the actual information) in each tile instance, needlessly bloating the entire collection from the original, say, 14 Mbytes to a whopping 30. Multiply this by several model instances, and you get hundreds of Mbytes just needlessly wasted due to poor design.
Finally, I was thinking about using some sort of mediation, where a tile itself would be a key that could be used to resolve who its parent was by some 3rd-party mediator, and in this case the access to this mediator could be static. For some models this is really neat, especially if there is some piece of information that could be used to cleanly transform the child’s identity into a pointer to its parent, but for the simpler ones all of this is just an overdesign and an implementation liability.
Please bear in mind that a) this question is not really about tiles, the actual designs I regularly stumble upon are typically more developed and convoluted where this matter of placing API where it belongs isn’t just my OCD but there are actually deeper reasons for separating concerns, and b) I don’t really have a technical problem with any of this, it’s not that I’m stuck or that I cannot compromise with it, I’m mostly philosophically interested in achieving a better design, in hearing out different solutions and listening to potential workarounds for this particular case.
Especially if they are simple to implement, easy to maintain, and fulfill this basic requirement of not wasting memory just because we can.
I am well aware that 30 Mbytes of extra memory is nothing for todays computers, so please don’t offer “RAM is cheap and not a problem nowadays” as an answer or excuse, because this is not a question I’m asking and will likely only clutter the thread. And besides we all have StackOverflow for such answers anyway
So, that aside, what are your thoughts on this?! How do you normally approach this situation? (And thanks for reading and any replies.)