Jump to content
  • Advertisement
Sign in to follow this  
derefed

Seemingly innocuous design problem

This topic is 3787 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Let's say that I have two classes: Player and Room. A Room holds a vector of pointers to Players that are within it, and thus can grab hold of any of them and call their methods; for instance, in situations where, say, the room is full of poison and drains everyone's HP gradually. A Player holds a pointer to the Room it's in, both so it knows its location and so that it can call the Room's methods; for instance, if the Player wanted to grab a list of names of those in the room with him. These are just examples (and somewhat poor ones), so above all, just assume that both Players and Rooms must be able to call each others' methods. Now then, this obviously creates a cyclic dependency, which I've always been told to avoid at all costs (both by others and by my compiler (and yes, I know that in .01% of cases it's not bad, but you know what I'm getting at)). So in the game I'm currently crafting (a MUD, FYI), I've taken care of this problem by making such classes purely data classes (structs), giving each instance a unique ID, and using classes defined later to handle their interactions by retrieving their data by ID and manipulating it. Now, this is fine for certain things, but when it comes down to actions such as "Heal" or "Deal_Damage", well... these sorts of methods really strike me as those that belong with the actual Player class itself, rather than in some other class. I guess my overall point is that the struct type thing is all right, but it kind of seems like I'm defeating the purpose of OO altogether. I mean, defining methods in other classes to handle the manipulation of data from another class doesn't seem much different than the old C way of making structs and outside functions that use them. So how am I supposed to actually use classes the way I want to without creating cyclic dependencies? Is there a design pattern for this?

Share this post


Link to post
Share on other sites
Advertisement
Not everything in the world is solved via design patterns. This for example is simply bad requirements. If Player must know about Room and Room must know about player... you're by definition going to have some sort of mutual knowledge situation. Not the end of the world, but it does tend to lead to a few problems. The solution is to fix the requirements so one class doesn't know of the other. Having the player care about where it's located seems like the one that should go.

That'd then require 'move player' sort of commands to exist on the map level or higher... somewhere that knows of the arrangement of rooms. Though such a design is 'cleaner' it might prove less practical, and going with just the mutual knowledge setup might be better towards 'getting stuff done'.

Share this post


Link to post
Share on other sites
Quote:
Original post by derefed
Let's say that I have two classes: Player and Room. A Room holds a vector of pointers to Players that are within it, and thus can grab hold of any of them and call their methods; for instance, in situations where, say, the room is full of poison and drains everyone's HP gradually. A Player holds a pointer to the Room it's in, both so it knows its location and so that it can call the Room's methods; for instance, if the Player wanted to grab a list of names of those in the room with him.

These are just examples (and somewhat poor ones), so above all, just assume that both Players and Rooms must be able to call each others' methods. Now then, this obviously creates a cyclic dependency

There's nothing wrong with having two classes that reference each other (cyclic dependency)... I wouldn't even say that it's bad in most cases. There's lots of situations, for example, where a two-way pointer may be required...

Use forward declarations:

room.h
------
class Player;
typedef std::vector<Player*> PlayerVec;
class Room
{
private:
PlayerVec m_Players;
public:
PlayerVec::const_iterator GetPlayerIteratorBegin() const
{ return m_Players.begin(); }
PlayerVec::const_iterator GetPlayerIteratorEnd() const
{ return m_Players.end(); }
}

player.h
------
class Room;
class Player
{
private:
Room* m_CurrentRoom;
public:
const Room* GetCurrentRoom() const { return m_CurrentRoom; }
}






Now in your .cpp files, you can include both of these headers (in any order) and Rooms can call Player's functions and Players can call Room's functions.

Share this post


Link to post
Share on other sites
I think the relational approach is really underutilized. There is a set of players, there is a set of rooms, and there is a relation between them: each player is in one room. The relation is a separate entity. Having players and rooms maintain pointers to each other is a possible optimization in this sort of system. As long as you're going with the data oriented approach, you can just think of the relation as being another peice of data. The object oriented approach seems like a step backwards if it has difficulty representing such a simple interaction between components.

Share this post


Link to post
Share on other sites
Quote:
Original post by Vorpy
I think the relational approach is really underutilized. There is a set of players, there is a set of rooms, and there is a relation between them: each player is in one room. The relation is a separate entity.


Hmm... could you please elaborate on this a little more?

Share this post


Link to post
Share on other sites
From the design level, I don't think there's any need to get into pointers and references. In a MUD, there are obviously players and rooms. In your design, you want each player to be in one room. It doesn't really matter if the player has a pointer to a room, or a room has pointers to the players in it, or if there is a table containing one entry for each player that tracks which room each player is in. All that matters is that at some conceptual level, each player is in a room.

The actual implementation of the relationship is an implementation detail. All that matters is that you have a way to apply a function to all the players in a given room. You can even write a higher level function that applies a function to all players in a given room. Maybe the higher level function uses the list maintained by the room, or maybe it uses a database query to retrieve all the players in the room.

Share this post


Link to post
Share on other sites
i don't personally see a problem with mutual dependence. Yes, it is nice to avoid, but at times like this it just makes sense. I would just give each object a pointer to it's room or players. Just make sure everybody is informed if a player dies or you obviously segfault.

In the Real World you have to balance design coolness with actually getting work done.

Share this post


Link to post
Share on other sites
Personally I don't see a problem with the current structure. I'd probably just add an extra interface so that Player objects talk to an IRoom (or IEnvironment) instead of a concrete Room class. This would have the handy side effect of allowing your players to exist in an CSwamp, CForest etc. without any extra work.

Share this post


Link to post
Share on other sites
I don't think you have a mutual dependency problem between two objects. There are three in this situtation: Room, Player, and Poison. Poison should be able to detect the Players in the Room to deliver damage to them.

Having said that, if the Room does have some inherit (not in the OO sense) affect on Players then there is a real two-way dependency. Just implement it that way!

Mutual dependency creates tight coupling which in some scenarious impedes the future value of objects for reusability. I just don't believe you are dealing with such a scenario here. I wouldn't want a Sprite object to have a dependency on a Player, but Room and Player belong in the same domain.

Share this post


Link to post
Share on other sites
Mutual dependencies aren't always bad, or sometimes they're a necessary evil.
This scenario isn't inherently bad or evil, but from the way you've posed the problem I tend to agree with Telastyn; that is to say your requirements are a bit smelly. Whether or not that's really true depends on what the Player and Room classes look like or what they need to know from one another.

For this post, I'm going to take the view that Player needn't know about the Room and suggest an alternative solution...
Quote:
Original post by derefed
A Room holds a vector of pointers to Players that are within it, and thus can grab hold of any of them and call their methods
Fine.

Quote:
for instance, in situations where, say, the room is full of poison and drains everyone's HP gradually.
Fine.

So, the Room class iterates an instance of PlayerList (where PlayerList is a typedef of a vector of Player pointers), and calls appropriate methods on Players.

Quote:
A Player holds a pointer to the Room it's in, both so it knows its location
The player probably needn't care about *which* specific room it's in, i.e. it doesn't have to care whether the room is a dungeon in the far left corner of the world map, a kitchen in the middle of the world map or an accountants office at the top of the world map.

The player's actual coordinate position probably ought to be the known by the player anyway.

So holding a pointer to a room for this purpose isn't necessary.

Quote:
and so that it can call the Room's methods; for instance, if the Player wanted to grab a list of names of those in the room with him.

You didn't specify under what situation the player needs a list of the other players he's in the room with, so I'm going to assume the player has an update() method and it acquires this list from within that.

So it might look like this:

class Player
{
public:
void update()
{
PlayerList players = currentRoom->getPlayerList();

/*
Other stuff
*/

}

private:
Room* currentRoom;
};



Certainly it shouldn't be the responsibility of the Player to maintain the list of players in a room, so we can confidently say that the Player must acquire this information through some means.

From the way you seemed to set out the problem, the Player class makes a method call to acquire this information, as shown in the snippet above.
I propose this list, along with other such data, is passed as a parameter. This is after all the whole purpose of parameter passing [smile].

If we do this, there is no need for the player to maintain a pointer to an instance of Room.
So it gives the following:

class Player
{
public:
void update(PlayerList& players)
{
/*
Other stuff
*/

}
};



The Player class is now unaware of a Room, it only knows about a list of Players (a list of instances of itself). The mutual dependency has gone as the Room knows about Players but not vice-versa. The Player class is completely oblivious to whomever or whatever owns the PlayerList; it could be a Room, a Swamp or a PlayerUpdator.

Whether the Room itself iterates the Player instances and calls update or whether a completely separate method (like the PlayerUpdator) acquires the list of players from the Room and calls update on Players, doesn't really matter.

The real point to make from all of this, is that it doesn't just apply to PlayerLists. Any data that previously the Player would have to have used a get-method on the Room class for, such as getRoomDimensions(), ought to now be passed as a parameter, such as update(int areaWidth, int areaHeight).

And that's it,
I hope it was at-least some help [smile]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!