6

I have two different classes; a Player and a Group. I need to be able to query a Player which Groups they are registered to (player.getGroups()), and which Players are registered to a Group (group.getPlayers()) quickly. The simplest solution is to have each Player keep a list of the Groups it's registered to, and each Group keep a list of all players registered to that group.

This introduces some data integrity problems (how do I ensure that if a Player references a Group, the Group also references the Player) and problems of how to add a Player to a Group.

A solution to the latter problem is to have a single method on either the Player or the Group:

public class Player {

    // ...

    public void registerToGroup(Group group) {
        this.groups.add(group);
        group.addPlayer(player);
    }

    // ...

}

This works, but also introduces some side consequences of modifying the Group you pass, which you might not expect. But maybe this is the best (and relatively simple) way to do it?

What are the best practices for these kinds of problems? I would prefer not have to search through lists and filter out a certain Player to find out which Groups that Player is registered to, and I would prefer to not mix in databases as this is quite a small project but has to be able to be expanded on without much trouble.

  • 2
    People will warn you about circular references - don't listen to them. They are cargo cult programming module responsibility as an argument for data references. In fact - it is perfectly fine to have these references, ORMs like Hibernate (and NHibernate and EF) have them in Java, Python and Ruby ORMs also have them. People are confusing mixing data and mixing module responsibility. Make sure your design is clear and your data is modelled in a way that makes sense - that's a lot more important than "not having a back reference". – Benjamin Gruenbaum Jul 3 '15 at 17:56
  • Is this just in-memory, or are you storing this in a database? – Winston Ewert Jul 3 '15 at 23:23
  • @WinstonEwert It's all in memory, which will be saved to a file for permanent storage but no databases are involved. – Havsmonstret Jul 4 '15 at 8:31
1

First and foremost, determine what is more common in your system - to list groups of a specific player or to list players of a specific group. Depending on which case is more common, implement it accordingly. If you try to implement both equally, you will take a performance hit for both.

So, assumption goes Player belongs to a group, so:

public class Group {
    public void AddPlayer(Player player) {

    }

    public void AddPlayers(IEnumerable<Player> player) {

    }
}

Now that we got that out of the way.

public class Player {
    public Ienumerable<Group> GetRelevantGroups() {
        // Assuming you have some groups provider in this class. You should anyways.
        return this._groupsService.GetGroupsWithPlayer(this.Id);
    }
}

Using both:

// Adding a bunch of players to a group.
myGroup.Add(players);

// Getting all groups for specific player.
var playerGroups = player.GetRelevantGroups();
| improve this answer | |
  • 1
    N-N relations are common. While in this case there may be no need to define one, saying that they don't exist (which you're doing) is blatantly false. – jwenting Jul 3 '15 at 17:38
  • I am not saying they don't exist, I am saying you have to pick a frame of reference. I have 2 dogs, but both of my dogs are also my wife's dogs, also, both dogs have 2 owners... but in the end, dog belongs to me, and not I belong to the dog. Same for everything else. – Alexus Jul 3 '15 at 17:42
  • 1
    not really. You can have things where there's ambiguity or the relation depends on who's viewing it. E.g. system I work in has machines. According to you, you're the leading authority in your dog/person relationships. Your dogs probably think otherwise (and if they were cats they'd definitely think otherwise). – jwenting Jul 3 '15 at 17:45
  • Sure they can, and that's why you pick precedence of what is more common in your system. As a developer you have to take that decision to take the least performance impact. If you fail to do so, your will loose performance for all of your viewers. – Alexus Jul 3 '15 at 17:46
  • Let me reword my answer to clear this confusion. – Alexus Jul 3 '15 at 17:47
1

This works, but also introduces some side consequences of modifying the Group you pass, which you might not expect. But maybe this is the best (and relatively simple) way to do it?

From a design perspective, to keep both lists in sync (if you want to handle it this way) is neither the responsibility of the player nor the group. You could design this with a mediator (here in the simplest form):

public class Group {

    List<Person> members;


    void addMember(Person p){
        members.add(p);
    }

}

public class Person {

    List<Group> groups;

    void addGroupMembership(Group g){
        groups.add(g);
    }
}

public class GroupingService {

    public void joinPersonToGroup(Person p, Group g){
        p.addGroupMembership(g);
        g.addMember(p);
    }

}

But besides: is there any reason to implement it that way?

| improve this answer | |
1

I solved this problem once with something like this:

class RelationshipManager {
    void addRelation(RelatableObject a, RelateableObject b);
    void removeRelation(RelatableObject a, RelatableObject b);

    List<C> getRelated(RelatableObject obj, Class<C> c);
}

The RelationshipManager keeps track of which objects are related to other objects. I can then make requests like:

relationshipManager.getRelated(player, Group.class);

To get all of the groups for a particular player.

You can go a step further and add methods to your class which call this:

class Player {

    List<Group> getGroups() {
       return relationshipManager.getRelated(this, Group.class);
    }
    void addGroup(Group group) {
       relationshipManager.addRelation(this, group);
    }
}

In my case, I was in python, so I actually did something like:

class Player:
    groups = HasMany(Group)

and HasMany overloaded the default getter/setter behavior of python to call into the relationship manager. I also has a BelongsTo which dealt with single elements instead of lists.

| improve this answer | |
0

Your biggest problem is that you're creating circular references which can lead to a world of problems, from endless loops to memory leaks.
In a database you'd create a separate entity (table) to contain the references, translated into code that's a class with a reference to both a Player and a Group.
Iterating over a collection of those will then give you all players in a group or all groups a player belongs to, but it can get to be an expensive operation.

| improve this answer | |
  • And it will be expensive primarily because you haven't defined what is more common in your system - to list players in the groups or to list groups of each player - basically you haven't picked your frame of reference - not a very efficient design, though it will do. – Alexus Jul 3 '15 at 17:44
  • oh yeah, it is expensive. But it might be the only way, hopefully you won't need to query that thing a lot (or it doesn't contain a lot of data). In practice you're likely going for a hybrid, having the most frequently accessed direction maintain a direct list and the other side gets queried through the link table. – jwenting Jul 3 '15 at 19:59
  • Yep, that I agree with, hence why I was saying "pick your poison in the beginning" :) – Alexus Jul 3 '15 at 20:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.