Kapitel 12 Versionssteuerung mit new und override
Softwareprojekte liegen selten in nur einer Codeversion
vor, die nie überarbeitet wird, es sei denn, die Software erblickt
nie das Licht der Welt. In den meisten Fällen möchte der Softwareprogrammierer
Änderungen vornehmen können, und der Client muss sich an diese
Änderungen anpassen.
Die Handhabung solcher Probleme wird als Versionssteuerung
bezeichnet und gehört zu den schwierigeren Aufgaben bei der Softwareentwicklung.
Ein Grund hierfür ist der, dass Planung und vorausschauendes Denken
gefragt sind. Mögliche Bereiche für Änderungen müssen
ermittelt werden und das Design muss Änderungen ermöglichen.
Ein weiterer Punkt, der die Versionssteuerung schwierig macht, besteht darin, dass die meisten
Ausführungsumgebungen dem Programmierer hierbei nicht viel Unterstützung
bieten. In C++ verfügt der kompilierte Code über interne Informationen
zu Größe und Layout aller enthaltenen Klassen. Wenn Änderungen
umsichtig vorgenommen werden, können die Klassen überarbeitet
werden, ohne dass alle Benutzer eine Neukompilierung durchführen
müssen; die auferlegten Beschränkungen sind jedoch gravierend.
Wenn eine Kompatibilität nicht länger gewährleistet ist,
müssen alle Benutzer zur Verwendung der neuen Version eine Neukompilierung vornehmen. Dies muss nicht unbedingt schlimm sein,
obwohl das Installieren einer neuen Bibliotheksversion dazu führen kann, dass Anwendungen nicht mehr
funktionieren, die ältere Versionen der Bibliothek verwenden.
Verwaltete Umgebungen, die keine Klassenmitglieder oder Layoutinformationen in den Metadaten offen legen, eignen sich besser
für die Versionssteuerung, dennoch ist es möglich, Code zu
schreiben, dessen Versionen nur schwer gesteuert werden können.
12.1 Ein Beispiel zur Versionssteuerung
 
Im folgenden Code wird ein einfaches Beispiel für
die Versionssteuerung dargestellt. Das Programm verwendet eine Klasse
namens Control, die durch eine andere Firma bereitgestellt
wird.
public class Control
{
}
public class MyControl: Control
{
}
Während der Implementierung von MyControl
wird die virtuelle Funktion Foo() hinzugefügt:
public class Control
{
}
public class MyControl: Control
{
public virtual void Foo() {}
}
Dies funktioniert gut, bis ein Aktualisierungshinweis
vom Lieferanten des Control-Objekts eintrifft. Die neue
Bibliothek umfasst eine virtuelle Funktion namens Foo()
für das Control-Objekt.
public class Control
{
// neu hinzugefügte virtuelle Funktion
public virtual void Foo() {}
}
public class MyControl: Control
{
public virtual void Foo() {}
}
Die Tatsache, dass Control als Funktionsname
Foo() verwendet, ist lediglich Zufall. In der C++-Umgebung
nimmt der Compiler in diesem Fall an, dass die Foo()-Version
in MyControl den Effekt hat, den ein virtual override
von Foo() in Control haben sollte und ruft
blindlings die Version in MyControl auf.
Das ist schlecht.
In der Java-Umgebung geschieht dies auch, hier liegt der Fall unter
Umständen jedoch noch schlimmer: Wenn die virtuelle Funktion nicht
über die gleichen Parameter und Mitgliedstypen verfügt, betrachtet
der Klassenlader das Foo() in MyControl als
unzulässige Außerkraftsetzung von Foo() in Control,
was dazu führt, dass die Klasse zur Laufzeit nicht geladen werden
kann.
In C# und der .NET-Laufzeitumgebung wird eine mit virtual definierte Funktion
immer als Stamm für ein virtuelles Dispatching betrachtet. Wird
eine Funktion in einer Basisklasse eingeführt, die als virtuelle
Basisfunktion einer vorhandenen Funktion betrachtet werden könnte,
bleibt das Laufzeitverhalten unverändert.
Wenn die Klasse anschließend kompiliert wird,
erzeugt der Compiler jedoch eine Warnung, in der der Programmierer aufgefordert
wird, den beabsichtigten Versionszweck anzugeben. In unserem Beispiel
wird der Modifikator new vor der Funktion eingefügt,
um als Standardverhalten festzulegen, dass die Funktion nicht außer
Kraft gesetzt wird.
class Control
{
public virtual void Foo() {}
}
class MyControl: Control
{
// keine Außerkraftsetzung
public new virtual void Foo() {}
}
Das Vorhandensein von new unterdrückt
die Warnung.
Wenn andererseits die abgeleitete Version eine Außerkraftsetzung
der Funktion in der Basisklasse sein soll, wird der Modifikator override
verwendet.
class Control
{
public virtual void Foo() {}
}
class MyControl: Control
{
// Außerkraftsetzung von Control.Foo()
public override void Foo() {}
}
So wird dem Compiler mitgeteilt, dass die Funktion
tatsächlich außer Kraft gesetzt wird.
|