Ein grundlegendes Problem, auf das ich recht häufig stoße, für das ich aber noch nie eine saubere Lösung gefunden habe, besteht darin, dass man ein Verhalten für die Interaktion zwischen verschiedenen Objekten einer gemeinsamen Basisklasse oder Schnittstelle programmieren möchte. Um es ein wenig konkreter zu machen, füge ich ein Beispiel ein;
Bob hat an einem Strategiespiel gearbeitet, das "coole geografische Effekte" unterstützt. Dabei handelt es sich um einfache Einschränkungen, z. B. werden Truppen, die im Wasser laufen, um 25 % verlangsamt. Wenn sie auf Gras laufen, werden sie um 5% verlangsamt, und wenn sie auf Pflaster laufen, werden sie um 0% verlangsamt.
Die Geschäftsleitung hat Bob mitgeteilt, dass sie eine neue Art von Truppen benötigt. Es sollte Jeeps, Boote und auch Hovercrafts geben. Außerdem sollten die Jeeps Schaden nehmen, wenn sie ins Wasser fahren, und Hovercrafts sollten alle drei Geländetypen ignorieren. Gerüchten zufolge könnte ein weiterer Geländetyp hinzukommen, der noch mehr Funktionen hat, als Einheiten zu verlangsamen und Schaden zu nehmen.
Es folgt ein sehr grobes Pseudo-Code-Beispiel:
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...
*/
}
}
Wie Sie sehen können, wäre es besser gewesen, wenn Bob von Anfang an ein solides Designdokument gehabt hätte. Mit der wachsenden Anzahl von Einheiten und Geländetypen steigt auch die Komplexität des Codes. Bob muss sich nicht nur Gedanken darüber machen, welche Elemente der Einheitenschnittstelle hinzugefügt werden müssen, sondern er muss auch eine Menge Code wiederholen. Es ist sehr wahrscheinlich, dass neue Geländetypen zusätzliche Informationen benötigen, die über das hinausgehen, was über die grundlegende IUnit-Schnittstelle abgerufen werden kann.
Jedes Mal, wenn wir eine weitere Einheit ins Spiel bringen, muss jedes Gelände aktualisiert werden, um mit der neuen Einheit fertig zu werden. Dies führt natürlich zu einer Menge Wiederholungen, ganz zu schweigen von der hässlichen Laufzeitprüfung, die den Typ der zu behandelnden Einheit bestimmt. Ich habe in diesem Beispiel die Aufrufe an die spezifischen Subtypen weggelassen, aber diese Art von Aufrufen sind notwendig, um sie durchzuführen. Ein Beispiel wäre, dass die Schiffsschraube beschädigt wird, wenn ein Boot auf Land trifft. Nicht alle Einheiten haben Propeller.
Ich bin mir nicht sicher, wie diese Art von Problem genannt wird, aber es handelt sich um eine Many-to-many-Abhängigkeit, die ich nur schwer entkoppeln kann. Ich habe keine Lust, 100 Überladungen für jede IUnit-Unterklasse auf ITerrain zu haben, da ich eine saubere Kopplung erreichen möchte.
Jeder Hinweis auf dieses Problem ist sehr willkommen. Vielleicht denke ich zu weit weg von der Umlaufbahn?