A basic problem I run into quite often, but ever found a clean solution to, is one where you want to code behaviour for interaction between different objects of a common base class or interface. To make it a bit concrete, I'll throw in an example;
Bob has been coding on a strategy game which supports "cool geographical effects". These round up to simple constraints such as if troops are walking in water, they are slowed 25%. If they are walking on grass, they are slowed 5%, and if they are walking on pavement they are slowed by 0%.
Now, management told Bob that they needed new sorts of troops. There would be jeeps, boats and also hovercrafts. Also, they wanted jeeps to take damage if they went drove into water, and hovercrafts would ignore all three of the terrain types. Rumor has it also that they might add another terrain type with even more features than slowing units down and taking damage.
A very rough pseudo code example follows:
public interface ITerrain
{
void AffectUnit(IUnit unit);
}
public class Water : ITerrain
{
public void AffectUnit(IUnit unit)
{
if (unit is HoverCraft)
{
// Don't affect it anyhow
}
if (unit is FootSoldier)
{
unit.SpeedMultiplier = 0.75f;
}
if (unit is Jeep)
{
unit.SpeedMultiplier = 0.70f;
unit.Health -= 5.0f;
}
if (unit is Boat)
{
// Don't affect it anyhow
}
/*
* List grows larger each day...
*/
}
}
public class Grass : ITerrain
{
public void AffectUnit(IUnit unit)
{
if (unit is HoverCraft)
{
// Don't affect it anyhow
}
if (unit is FootSoldier)
{
unit.SpeedMultiplier = 0.95f;
}
if (unit is Jeep)
{
unit.SpeedMultiplier = 0.85f;
}
if (unit is Boat)
{
unit.SpeedMultiplier = 0.0f;
unit.Health = 0.0f;
Boat boat = unit as Boat;
boat.DamagePropeller();
// Perhaps throw in an explosion aswell?
}
/*
* List grows larger each day...
*/
}
}
As you can see, things would have been better if Bob had a solid design document from the beginning. As the number of units and terrain types grow, so does code complexity. Not only does Bob have to worry about figuring out which members might need to be added to the unit interface, but he also has to repeat alot of code. It's very likely that new terrain types require additional information from what can be obtained from the basic IUnit interface.
Each time we add another unit into the game, each terrain must be updated to handle the new unit. Clearly, this makes for a lot of repetition, not to mention the ugly runtime check which determines the type of unit being dealt with. I've opted out calls to the specific subtypes in this example, but those kinds of calls are neccessary to make. An example would be that when a boat hits land, its propeller should be damaged. Not all units have propellers.
I am unsure what this kind of problem is called, but it is a many-to-many dependence which I have a hard time decoupling. I don't fancy having 100's of overloads for each IUnit subclass on ITerrain as I would want to come clean with coupling.
Any light on this problem is highly sought after. Perhaps I'm thinking way out of orbit all together?