Jump to content
  • Advertisement
Sign in to follow this  
BEngKohn

Two OOP encapsulation questions

This topic is 4258 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

I am doing some programming in C# and I keep running into the issue of a class I am designing not knowing enough about the data. Let me give an example. Say I have a Unit class which I use to create an instance for every army on my map. To keep track of them all I use an array or list. When a given unit is killed, I'd like to be able to call something like myUnit.die() but my conceptual problem is that then the unit would have to have access to the whole list of units to remove itself from. I could do something like call myUnit.die(myUnitList), but that seems wrong. I've got to be missing something, I think. My second question is related. I have a map class to hold the data for movement costs in a grid. It seems that it is getting really crowded in the Map class, however. For example, while doing my A* pathfinding, the routines need access to the movement cost data. So I put the routines in the Map class, so I can call something like myPath = myMap.CalculatePath(point start, point stop). While this works just fine, Conceptually it seems like I'd like to make things cleaner by creating something like an AStar class to handle any kind of AStar work I have. However, that would mean passing in the movement cost grid into the AStar when I call it. Is that a bad thing? I just have visions of huge amounts of data being passed instead of a reference if I do it wrong. Is that sort of passing huge chunks of data common in real applications? Thanks in advance!

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by BEngKohn
When a given unit is killed, I'd like to be able to call something like myUnit.die() but my conceptual problem is that then the unit would have to have access to the whole list of units to remove itself from. I could do something like call myUnit.die(myUnitList), but that seems wrong. I've got to be missing something, I think.


You're right. The unit class should not be aware that its instances will be stored somewhere, let alone rely on the type of storage.

The main possibility here is to setup a listener system. The unit has a dies event which first whenever the unit is killed, and external objects are allowed to register event handlers associated to the death event. When you store an object in a container, and want the object to be removed from the container when it dies, you simply add an event handler that removes the object from the container to the set of handlers bound to the dies event.

This way, the unit does not have to know about who will use it, and the container can transparently have it unregister itself upon death.

Quote:
My second question is related. I have a map class to hold the data for movement costs in a grid. It seems that it is getting really crowded in the Map class, however.

For example, while doing my A* pathfinding, the routines need access to the movement cost data. So I put the routines in the Map class, so I can call something like myPath = myMap.CalculatePath(point start, point stop).

While this works just fine, Conceptually it seems like I'd like to make things cleaner by creating something like an AStar class to handle any kind of AStar work I have. However, that would mean passing in the movement cost grid into the AStar when I call it.


Not necessarily. You could also decide that the pathfinding system is persistent (that is, you create it once for each map, it performs some precomputations, and then answers all queries). This way, you only have to pass the movement cost grid when you instantiate the system.


// Class member
PathFinder pathFinder;

// Initialize once
this.pathFinder = new AStar(this.map);

// use many times
myPath = this.world.pathFinder.ComputePath(start,stop);


Besides, unless the map is a struct or base type, it will be passed by reference, which involves no copy.

Share this post


Link to post
Share on other sites
Edit: OK, you wanted C#, the code is C++, but i'm pretty sure you can understand this quite easily [smile]

Quote:
Original post by BEngKohn
I am doing some programming in C# and I keep running into the issue of a class I am designing not knowing enough about the data. Let me give an example.

Say I have a Unit class which I use to create an instance for every army on my map. To keep track of them all I use an array or list.

When a given unit is killed, I'd like to be able to call something like myUnit.die() but my conceptual problem is that then the unit would have to have access to the whole list of units to remove itself from. I could do something like call myUnit.die(myUnitList), but that seems wrong. I've got to be missing something, I think.

No, you missed something.
The unit doesn't remove itself from the list - that doesn't have much sense. But someone is supposed to remove it (and to make it die).

The class whose responsability is to kill the unit also have the responsability to remove the unit from the list. The reason is that this class knows both the unit and the unit list. This should be encapsulated in a kill_unit() method:
void kill_unit(unit& the_unit)
{
the_unit.die();
unit_list.erase(
std::remove(
unit.list.begin(),
unit_list.end(),
unit),
unit_list.end());
}

This will allow you to modify the bahavior later if needed (for example, put the unit into a dying_unit list, for which a specific animation is played.
Quote:
My second question is related. I have a map class to hold the data for movement costs in a grid. It seems that it is getting really crowded in the Map class, however.

For example, while doing my A* pathfinding, the routines need access to the movement cost data. So I put the routines in the Map class, so I can call something like myPath = myMap.CalculatePath(point start, point stop).

While this works just fine, Conceptually it seems like I'd like to make things cleaner by creating something like an AStar class to handle any kind of AStar work I have. However, that would mean passing in the movement cost grid into the AStar when I call it.

A* is an algorithm - it's not a class. But you can implement it as a strategy: in this case the A* strategy will have it's own class. However, that shouldn't refrain you to create a cost_calculator class (a small class whose responsability is to compute the cost of a cell traversal).

My guess would be something along the line of:


class path_finding_strategy
{
public:
virtual bool calculate_path(const map& m,
const map_point& starting_point,
map_point& end_point);
};
class path_finder
{
path_finding_strategy& strategy;
public:
path_finder(path_finding_strategy& s) : strategy(s) { }

// end_point not const if it's allowed to be modified in the function
bool calculate_path(const map& m,
const map_point& starting_point,
map_point& end_point)
{
return strategy.calculate_path(m, starting_point, end_point);
}
};
class cell_traversal_cost_calculator
{
public:
typedef float cell_traversal_cost;
cell_traversal_cost compute_cost(const map_point& from
const map_point& to);
};
class path_finding_a_star : public path_finding_strategy
{
cell_traversal_cos_calculator cost_calculator;
public:
virtual bool calculate_path(const map& m,
const map_point& starting_point,
map_point& end_point)
{
// warning:
// will use cell_traversal_cos_calculator::compute_cost() quite
// extensively, so this function has to be quite speed.
}
};



I decoupled the map class and the path_finder class (because the map shouldn't be responsible for finding a path. It should be responsible for the map storage and access).
Of course, it means that the map class has no idea of what a path_finder is, meaning in turn that someone else is making the call.
Quote:
Is that a bad thing? I just have visions of huge amounts of data being passed instead of a reference if I do it wrong. Is that sort of passing huge chunks of data common in real applications?

Thanks in advance!

No, we tend to avoid passing huge amount od data to a called function - both for efficiency reason and for design reasons.

HTH,

Share this post


Link to post
Share on other sites
First of all, thank you for the help so far. Thinking about your answers, I realize I have a more basic question that could solve a lot of my problems.

Let us say we have three classes:
(1) a Map class, and instance of which will hold the grid data,

(2) a Display class that has a method Display.Show(device) which outputs the map to a graphics device, and

(3) a MiniMap class which has a method MiniMap.Show(device) which outputs the map (albeit in a specialized form) to a different device.

Is it OK (and actually good) to have a declaration in both Display and MiniMap of the following: private Map myMap;

In other words I need both the classes to have access to the (same instance of) map data, and in the constructor of each I could set their myMap to that one instance.

Am I creating more than one memory allocation for the map data? I don't want there the be three sets of the map grid, the instance I create and the two others within these classes when they get instantiated.

If not, how else do you do a has-a relationship when two different objects "have-a" so to speak.

Thanks again!

Share this post


Link to post
Share on other sites
It's possible, assuming that you can display a map using only its public data. Since you're using C#, your approach does not create new instances of Map (unless you explicitly ask for it, using operator new, or if Map is a struct instead of a class). Of course, you'll still have to pass the used Map instance as a constructor argument.

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.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!