I have an event class that holds details of a volunteering "Event" class. There is an "EXCO" Class that can create and edit details of said Events. There is also a "Volunteer user" class is allowed to register for such events.
My initial approach to this was to have a "Event DataBase" Class that holds an array of Events, and provides interfaces for the "EXCO" class to use to Create events, and delete events. This way, the "EXCO" class only interacts with the "Events" class via interfaces, which I hope is a good use of the DIP.
However, I have difficulty doing the same for the "Volunteer" and "Event" class. "Volunteer" can view and sign up for "Events". Hence I'm under the impression that the "Volunteer" class should CONTAIN a list of "Events".
Likewise, an "EXCO" should be able to check an "Event" and see which "Volunteers" have signed up. I'm also thinking of putting an array of "Volunteer" into "Event" class.
However, since these classes literally contain each other, I feel like there is a missing layer of abstraction between them, and it feels like a bad design since any changes to either class would affect the other.
Is my understanding of a good DP flawed or is there a better way to do this? Thanks!
2 Answers 2
Volunteers and Events don't contain but reference each other. Each can exist on its own.
In the real world, volunteers would have some kind of list of events they will participate in, most likely as entries of their calendar where they are mixed with dentist appointments and birthday reminders. Events (or rather their coordinators) have lists of participants.
You can implement a similar scheme within your application, but just as in the real world there is a risk that these lists diverge somehow.
An easier solution in the application would be to have a global register of event participation, which can be updated and queried by your EXCO class (and could be queried by volunteers and events to retrieve their event or participant lists). Of course such a global register can't exist in the real world.Sounds too technical/relational? Well it is, but software solutions to real world problems are not required to model the real world faithfully.
-
6When you have circular dependencies (or other, complex dependency graphs) between objects, that is often a sign that you either have too many objects (i.e. the objects should be one object), or, more often, that you are missing objects (i.e., the parts which cause the complex dependencies should be broken off into separate objects). In the latter case, the missing object is often a re-ification of the relationship, as is the case here. Remember: relationships can be objects, too! A classic example is de-registering an Observer from a Subject. If you re-ifiy the relationship into a SubscriptionJörg W Mittag– Jörg W Mittag2023年11月03日 10:40:20 +00:00Commented Nov 3, 2023 at 10:40
-
4... you can use the Subscription object to cancel the subscription.Jörg W Mittag– Jörg W Mittag2023年11月03日 10:40:40 +00:00Commented Nov 3, 2023 at 10:40
-
3The global register is a real world register. It’s only global to the application but has the limited scope of the data you may enter into a single instance of the application. Which is as realistic as it can get because, what would you use in the real world to implement such a register, the very application we’re looking at, perhaps?Holger– Holger2023年11月03日 15:44:19 +00:00Commented Nov 3, 2023 at 15:44
-
1+1 just for "not required to model the real world faithfully"falsedot– falsedot2023年11月03日 19:56:52 +00:00Commented Nov 3, 2023 at 19:56
-
1In the real world certain things certainly are related in certain ways, and we reify this to say that there are certain relationships in the real world. Whether anybody records them in any particular ways in the real world before you do is irrelevant.philipxy– philipxy2023年11月04日 02:08:27 +00:00Commented Nov 4, 2023 at 2:08
The principle I'd be most concerned with here is called:
Maintaining a consistent bidirectional relationship is not something you want to be doing manually. Do that through some abstraction that maintains it for you.
I feel like throwing hash sets at this is a premature optimization. Let's do something dead simple.
What if Register
is a List
of Commitment
s?
What if Commitment
was an object that had "Volunteer" and "Event" fields?
Now you have a single source of truth. You can add and delete Commitment
s in one place atomically. Once you show that looking things up in this is too slow, only then add hash tables that are built from this dead simple list of commitments we call the register. The hashes take either a volunteer or an event and return a list of commitments that hold both.
Done this way the possibly inconsistent hashes are not your source of truth. Just an optimization. The truth still lies with the list of commitments.
Sound complicated? Well you were going to need lists in the hashes anyway. If building this is slow then build it as you go.
And finally you can always break down and make the DB do this in a table with actual indexes.
The real trick is designing the interface so that the outside neither knows nor cares which implementation you’re using. Make it work with all of them and your using code can go untouched when you change how it works.