Sonntag, 18. November 2007

Wie man aus einer Ameise (k)einen Elephanten macht

Der Hauptgrund, der meist für die Verwendung von typisierten Sprachen genannt wird, ist, daß Typfehler schon zur Compilezeit abgefangen werden. Wenn man versucht, die Methode trompeten bei einer Ameise aufruft, sollte sich der Compiler beschweren. Bei älteren Java- und C#-Versonen ist das aber spätestens dann eine Illusion, wenn das Tier zwischenzeitlich in einer Collection war. Dort wir alles zum Object, wenn es aus der Collection herausgeholt wird, muß es dann wieder zum Elephanten gecastet wird. Wenn es dann doch eine Ameise war, gibt es einen Laufzeitfehler.

Mit Java 5 und C# 2.0 kamen nun die Generics. Wenn wir nun eine Collection Tierkäfig haben, dann können wir jetzt spezielle Käfige herstellen für Ameisen (Tierkäfig<Ameise>) oder Elephanten (Tierkäfig<Elephant>). Jetzt wird zur Compilezeit sichergestellt, daß in jeden Käfig die passenden Tiere kommen und ein Cast ist beim herausholen nicht mehr notwendig.

So, jetzt ist ja alles gut. Wir verwenden überall eifrig Generics. Auch junge, aufstrebende Architekten entwerfen ambitionierte Frameworks basierend auf Generics. Damit brauchen sie dann aber auch Methoden, die als Argument beliebige Tierkäfige verarbeiten können. Das klingt nun zuerst recht logisch, führt aber bald zu Problemen. Natürlich kann man einen Elephantenkäfig nicht einem Ameisenkäfig zuweisen. Aber auch ein Ameisenkäfig kann normalerweise nicht als Tierkäfig verwendet werden, denn dann könnte man aus dem Ameisenkäfig auch einen Elephanten holen - das gleiche Problem, das wir vor den Generics hatten.

Wenn wir Java verwenden, haben wir eine Lösung. Java kennt nämlich die Begriffe Covarianz und Contravarianz. Eine Zuweisung eines Ameisenkäfigs an einen Tierkäfig kann nämlich ungefährlich sein, wenn wir sicherstellen, daß wir nur allgemeine Tiermethoden aufrufen und keine Tiere hineintun, da wir nicht mehr die Art sicherstellen können. Das heißt Covarianz. In Java sieht das so aus:

String gibTierNamen(TierKäfig<? extends ITier> käfig)

In einen Ameisenkäfig können wir auch Waldameisen hineingeben. Das ist dann Contravarianz. In Java schreibt man dann statt extends super.

C# kennt (bislang, Version 3.0) noch keine keine Co- und Contravarianz. Wenn dann die Entwickler, die das Framework unseres jungen Architekten umsetzen, anfangen, auf Reflection zurückzugreifen, sollte der Architekt ins grübeln kommen:

public static String gibTiernamen(object käfig)

{

MethodInfo mInfo = käfig.GetType().GetMethod("Freilassen");

ITier tier = (ITier)mInfo.Invoke(käfig, null);

return tier.Name;

}

Wenn wir so programmieren, sollten wir doch lieber gleich eine dynamische Programmiersprache wie Python oder Ruby verwenden. Die können dies viel besser.

Wie kann sich nun in C# derzeit retten? Nun, in diesen allgemeinen Methoden wird auch nur allgemein auf das Tier zugegriffen und keine artspezifische Information verwendet. Man definiert einfach ein Interface, daß alle Methoden der Tierkäfigs enthält, die nicht von der Art abhängen. Dieses ist nicht generisch. So kann man jetzt dieses Interface in der allgemeinen Methode verwenden:

public static String gibTierNamen(ITierkäfig käfig)

{

return käfig.Tierart.Name;

}

Keine Kommentare: