Implementing many-to-many relationships
-
From your explanation it sounds to me that the site owns everything, and also may be needed to dispatch between the different components. As far as using goes, depending on exactly what you put in this word a publish-subscribe sort of architecture that is managed by the site may be reasonable and clean.
Personally, I don't think breaking encapsulation and coupling everything through some global map is a good idea. In a good architecture you couple things together only when you really need to.
For example the zone may own the equipment items and activities, or perhaps the site could initialize them and inject to the zone references to the relevant object(s). Similar considerations for a piece of equipment, does it really need to know what zone and what activity is using it to be able to function? If not then it absolutely doesn't depend on them and/or the dependency if such materializes can be injected from the owner (e.g. the site) whenever the need arises.EDIT: If you're wondering what I mean by injecting the dependency in this context: https://en.wikipedia.org/wiki/Dependency_injection
-
@kshegunov said in Implementing many-to-many relationships:
For example the zone may own the equipment items and activities
A zone will certainly use equipment and activities, but both of those could well be used in other zones. This where the notion of "ownership" becomes tricky.
Similar considerations for a piece of equipment, does it really need to know what zone and what activity is using it to be able to function?
Well...I'm not sure. If the user creates a schedule entry (an activity) that uses equipment A from 10 AM to noon, then later tries to create another entry from 11 AM to 1PM with the same equipment, this needs to fail. It would be cleaner if it fails at equipment selection time, so he can say "oh, OK, I'll use equipment B for this instead." Without keeping a list of activities within the equipment, I'll have to traverse all the activities in all the zones to determine this. Probably not a huge deal from a CPU standpoint, but could make for lousy code.
@kshegunov said in Implementing many-to-many relationships:
If not then it absolutely doesn't depend on them and/or the dependency if such materializes can be injected from the owner (e.g. the site) whenever the need arises.
EDIT: If you're wondering what I mean by injecting the dependency in this context: https://en.wikipedia.org/wiki/Dependency_injectionThanks for the reference. As it turns out, I've used the assembly form of injection in the past. It seems viable for this application, provided that the number of things that require mutual awareness stays low. These lists of course will be dynamic -- when a site is initialized, all lists will be empty until equipment discovery occurs, activities are created, etc.
-
@mzimmers said in Implementing many-to-many relationships:
Well...I'm not sure. If the user creates a schedule entry (an activity) that uses equipment A from 10 AM to noon, then later tries to create another entry from 11 AM to 1PM with the same equipment, this needs to fail. It would be cleaner if it fails at equipment selection time, so he can say "oh, OK, I'll use equipment B for this instead." Without keeping a list of activities within the equipment, I'll have to traverse all the activities in all the zones to determine this. Probably not a huge deal from a CPU standpoint, but could make for lousy code.
I don't follow. The equipment can keep the timeline for when it is used by anybody who reserved it. This doesn't require it to know what activities or zones or w/e it is used in. It just needs to know that somebody is going to use it at some point. You can immediately return an error if you try to reserve some equipment for some time, but it's reserved. E.g. (pseudocode)
class Equipment { public: bool reserve(const TimeSlot &); void free(const TimeSlot &); bool isFree(const TimeSlot &) const; private: QVector<TimeSlot> reserved; };
Then what the activity needs to keep is a list of time slots + equipment references.
class Activity { public: Activity(const QVector<Equipment *> &items); bool select(Equipment *, const TimeSlot &) { // ... check if the equipment is available, insert in timeTable, etc. } QHash<TimeSlot, Equipment *> timeTable; };
@mzimmers said in Implementing many-to-many relationships:
I'll have to traverse all the activities in all the zones to determine this.
Why? Is that the activity keeps track of when an equipment it used? Or rather I should paraphrase - should an activity manage the internal state of a piece of equipment? You could make an argument that the infotainment system in a car could control the fuel injection cycle, but then again, should it?
-
@kshegunov said in Implementing many-to-many relationships:
make an argument that the infotainment system in a car could control the fuel injection cycle, but then again, should it?
Sometimes I think mine does in my car....
-
@kshegunov said in Implementing many-to-many relationships:
the infotainment system in a car could control the fuel injection cycle, but then again, should it?
I believe, it would be indeed very entertaining, if it does ;-)
-
@kshegunov said in Implementing many-to-many relationships:
I'll have to traverse all the activities in all the zones to determine this.
Why? Is that the activity keeps track of when an equipment it used?
Imagine an installer who is creating a new activity. One of the first steps is to associate equipment with that activity. If the equipment has no knowledge of what other activities its already committed to, and therefore no knowledge of what its committed schedule is, each time the installer selects a new equipment item, the system will have to search all the activities, comparing their schedules and equipment lists to see whether there are conflicts with the new activity being created.
To use your example, the infotainment system doesn't control the fuel injection, but if something were to go wrong in the fuel injection system, the infotainment system might want to stop blasting "Stairway to Heaven" and alert the driver of the malfunction.
-
I would personally advice against using pointers-to-object. Its slower and more important, its more fragile.
It is more fragile because when you delete an object, all the pointers to it need to manually be updated / removed, and if you fail to do so you'll have crashes if you are lucky.You could go with weak-pointers, but that sounds like its just adding complexity and not solving the issue.
My suggestion is this;
Have one place that owns all your "stuff".
Likely one named list for each type of "stuff". So your have a class that has a list (in a map) of equipment, another list of activities etc.
This is in the form of aQMap<int, Equipment> e;
.Linking between those items then is using an integer. The integer you have as the key in your hash.
This means you need to do an extra step (a search in the map) to refer to anything, but its much more safe. Additionally, you can change your Equipment object to now not be 'new'-ed, but just a simple struct. Of which some members will be integers pointing to other maps of 'stuff'.
Its not very object oriented as one object would own all of them and would update and resolve the links between them. But its quite a bit easier to write and maintain.
postscript; if you must, you can make all of this the private content of a 'manager' class and provide some helper classes on top to make it look all object-oriented again. (example: manager, pretty-object, implementation).
-
@Pl45m4 said in Implementing many-to-many relationships:
I believe, it would be indeed very entertaining, if it does ;-)
Well, I get the sense that @mzimmers is eventually going to tell us if it is entertaining or not.
@TomZ said in Implementing many-to-many relationships:
This means you need to do an extra step (a search in the map) to refer to anything, but its much more safe.
That's a complete misnomer. How is this safer:
QMap<QString, Something> map; int foo = map.find("blah").value().doStuff(); // And of course if there's no "blah" that crashes just as well, so there's nothing intrinsically safer in a map, or a hash, or w/e
Cognate with:
class Equpment : public QObject { ... }; class Activity { QHash<TimeSlot, QPointer<Equipment>> timetable; };
Same consideration - you must check if what you're referring to actually exists.
-
@kshegunov said in Implementing many-to-many relationships:
How is this safer
Sure you can use weak pointers (or qpointers, or shared pointers), but that just uses more cycles to zero the pointers.
As I wrote in the original post:
You could go with weak-pointers, but that sounds like its just adding complexity and not solving the issue.
There is no difference between checking the null pointer and you checking the iterator be 'end()'. It doesn't solve the problem.
But the structs only solution itself is much easier to handle, much cheaper on resources (great for embedded) and generally easier to think about.
-
@TomZ said in Implementing many-to-many relationships:
Sure you can use weak pointers (or qpointers, or shared pointers), but that just uses more cycles to zero the pointers.
Cycles pretty much irrelevant in business code, I much rather rely on the prefetcher being smart enough to build its heuristic (which it really is!). The only place it truly matters is whenever you're low-level optimizing a hot codepath, like in a heavy calculation. I'd take the additional indirection any day if I can maintain the code more easily in the long-run, which is the typical case for business logic (which appears to be the case here).
-
@kshegunov said in Implementing many-to-many relationships:
Cycles pretty much irrelevant in business code
you can waste your cycles, for sure.
As I wrote, it doesn't actually solve the bigger issue, it only solves the problem you introduce when you use pointers. Don't use pointers and the problem doesn't need solving.
So my suggestion is to not use pointers.
-
@TomZ said in Implementing many-to-many relationships:
Don't use pointers and the problem doesn't need solving.
You can't not use pointers, and I imagine you know that. Neither in C, nor in C++, nor in assembly. Every heap allocation is a pointer, and even if you don't keep anything in the heap, which is dubious to begin with (with one notable exception), there are other pointers out there like C-like arrays, function pointers, lvalue references, etc.
-
@kshegunov said in Implementing many-to-many relationships:
You can't not use pointers,
Please look at the code sample I provided various comments back.
It also links to some kde git repo which shows this in practice.
Yes, you can absolutely code "without" pointers. Objects can live on stack, Qt does that in many situations by default for many objects.
You can have a QList of QStrings. No pointers in your code at all. Same idea.
-
Hey guys - while I (truly) appreciate the points being made, what I was really looking for in my OP was opinions on sensible approaches to architecting this application. I'm more concerned with readability and maintainability than memory leaks.
It appears that, apart from @SGaist 's original response, most people don't like the idea of intermediate tables, instead managing the relationships from within the respective objects...correct?
-
@mzimmers said in Implementing many-to-many relationships:
Hey guys - while I (truly) appreciate the points being made, what I was really looking for in my OP was opinions on sensible approaches to architecting this application. I'm more concerned with readability and maintainability than memory leaks.
Well, they aren't exclusive and I'd rather make the argument that you shouldn't have leaks at all.
It appears that, apart from @SGaist 's original response, most people don't like the idea of intermediate tables, instead managing the relationships from within the respective objects...correct?
I can't speak for everyone, but I certainly don't. I'd rather architect such an application in the usual OO way, where each item is responsible for whatever it is responsible, than to have the god object that knows everything about everybody.
@TomZ said in Implementing many-to-many relationships:
Yes, you can absolutely code "without" pointers. Objects can live on stack, Qt does that in many situations by default for many objects.
Yes, but sometimes, not always. Qt extensively uses owning and non-owning (weak) pointers to
QObject
s and that's perfectly fine. It will depend on the semantics, not the syntax (which is what a pointer is - syntax).You can have a QList of QStrings. No pointers in your code at all. Same idea.
When it's a data class, and that's only because Qt already wraps the pointer inside an object to hide that detail from you and give you value semantics. This doesn't mean this is applicable to everything, or rather that you should use it for everything. Not everything is a value, so not everything can behave this way.
-
@mzimmers said in Implementing many-to-many relationships:
opinions on sensible approaches to architecting this application.
I would architect it the way that I suggested. A bunch of structs that get stored in maps.
Relations between those objects happens via ints that are the 'keys' in those maps.Extremely easy to architect. It avoids memory overhead, avoids pointers and avoids the need to have a manager separate from your data classes as the manager IS your datastructure.
The "make it a nice API" is what I linked to the existing example which does exactly that in:
@TomZ said in Implementing many-to-many relationships:
postscript; if you must, you can make all of this the private content of a 'manager' class and provide some helper classes on top to make it look all object-oriented again. (example: manager, pretty-object, implementation).
I indeed hope we can get past the point where some don't understand it and say its a stupid idea. That's indeed not productive.
-
@TomZ said in Implementing many-to-many relationships:
avoids pointers
Umm, you mean it swaps pointers to memory for integer indexes into a map/array. No guarantee they are still valid/still have the same key as when they were created. Maybe less likely to "crash", more likely to refer to valid but incorrect information?