Ein Kapitalfehler bei OO-Programmierung und Design
Jetzt wird’s schwierig. Ich möchte mich heute ein wenig detaillierter mit einem essentiellen Konzept der Objekt-orientierten Programmierung und dessen OO-Design beschäftigen: Dem Einsatz von virtuellen Methoden.
Virtuelle Methoden sind eines der Kernkonzepte in der Objekt-orientierung, um Polymorphie zu ermöglichen. Das interessante an virtuellen Methoden ist ihre grundlegende Charakter-Eigenschaft, eine gewisse Aufruf- und Kontext-Konvention zu definieren, ohne das der Aufrufer der Methode die konkrete Implementierung oder besser das Verhalten der Methode hervorsehen kann.
Dieses Konzept in der Objektorientierung wurde im Laufe der Zeit stetig erweitert und verfeinert. So sind z.B. die abstrakten Methoden/Klassen sowie Interfaces eine Weiterentwicklung des Polymorphie-Gedankens von virtuellen Methoden. In C++ ist z.B. die Definition einer “puren” virtuellen Klasse (eine Klasse mit ausschließlich pur virtuellen Methoden) vergleichbar mit einem Interface in neueren Sprachen, wie z.B. C# oder Java.
Es ist folglich völlig nachvollziehbar, das der Einsatz von virtuellen Methoden in der objekt-orientierten Programmierung quasi unerläßlich ist, um bestimmte Konzepte umzusetzen. Nun ist es aber auch öfters der Fall, das die Zweckmäßigkeit des Einsatzes von virtuellen Methoden mißverstanden oder sogar mißbraucht wird. Des Öfteren begegne ich z.B. folgendem (oder ähnlichem) C#-Code:
public abstract class AnimalBase {
private Position pos;
public virtual Position GetPosition() {
return this.pos;
}
public virtual void Eat() {}
public virtual void Run(Position newPos) {
this.pos = newPos;
}
}
Solche und ähnliche Klassen bekomme ich ab und an auf meinem Screen zu sehen, wenn ich durch Quellcodes von Software durchgehe. Manchmal sieht man sogar wahre “Orgien” von protected virtual, protected internal virtual oder sogar public virtual.
Es ist meiner Meinung nach offensichtlich, das der Programmierer der obigen Klasse die Zweckmäßigkeit einer virtuellen Methode nicht oder nur teilweise verstanden hat. Alle Methoden sind virtuell. Stellt man den Entwicklern solcher Klassen die Frage, warum alle Methoden virtualisiert wurden, bekommt man meistens folgende Antwort: “Die Klasse soll erweiterbar und spezialisierbar bleiben”.
Tatsächlich war es vor allem in den Anfangsjahren der Objekt-orientierung gang und gäbe, wirklich alle Methoden zu virtualisieren, um der Idealvorstellung der guten Erweiterbarkeit und Spezialisierung näher zu kommen. Vor allem “OO-Theoretiker” hatten in den Gründungsjahren gepredigt, das Konzept der virtuellen Methoden genügend auszuschöpfen. Vor dem Hintergrund dessen ist es z.B. auch nicht mehr so verwunderlich, das im Gegensatz zu C# bei Java alle Methoden standardmäßig virtuell sind.
Wenn dem so ist, das viele den massiven Einsatz von virtuellen Methoden bevorzugen, was ist dann daran so verkeht?
Virtual Insanity
Das Problem von übereifrigem Einsatz virtueller Methoden versteckt sich gut, dementsprechend sind sie auch nicht so offensichtlich, jedoch nicht minder schwerwiegend: Das Verhalten - oder besser die “Stabilität” der Implementierung einer Klassenhierarchie ist nicht gewährleistet.
Ergo: stellt man eine Basisklasse mit sehr vielen virtuellen Methoden zur Verfügung, so kann das eigentlich gewünschte Verhalten der Basisklasse sowie deren Kindklassen nicht mehr vollends und sicher gewährleistet werden. Angewendet auf das obige Beispiel wird die Problemstellung etwas deutlicher:
public class Snake : AnimalBase {
private Position pos;
public override void Run(Position newPos) {
this.Creep(newPos);
}
public virtual void Creep(Position newPos) {
while (!this.pos.Equals(newPos)) {
this.pos.X += Math.Sign(newPos.X);
this.pos.Y += Math.Sign(newPos.Y);
this.pos.Z += Math.Sign(newPos.Z);
this.UpdateCreepState();
}
}
protected virtual void UpdateCreepState() {
/* some code */
}
}
Auf den ersten Blick wirkt die Implementierung der Snake-Klasse plausibel. Dennoch führt die Implementierung zu einem massiven Fehler. Die “Standard-Implementierung” der GetPosition()-Methode von AnimalBase ist bei der Snake-Klasse nicht mehr operabel.
Bei einem so einfachen Beispiel mit wenigen Methoden ist das relativ offensichtlich - wäre aber die AnimalBase-Klasse eine komplexe Klasse (oder Klassen-Hierarchie) mit vielen virtuellen Methoden, so ist es selbst für geübte OO-Hasen nicht mehr überschaubar, welchen Einfluß man auf die Implementierung der Basisklassen ausübt, wenn man eine oder mehrere virtuelle Methoden überschreibt.
Für Eric Gunnerson, einer der C#-Gurus und C#-Program Manager (Microsoft), ist das oben gezeigte Problem das Hauptproblem exzessiver Verwendung virtueller Methoden. Für ihn ist der massive Einsatz von virtuellen Methoden einer der 7 Todsünden in der OO-Programmierung.
Mittlerweile sind sogar einige “OO-Theoretiker” davon überzeugt, dass der massive bzw. ausschließliche Einsatz von virtuellen Methoden wirklich keine gute Idee ist. Das liegt vor allem daran, das sich in der Praxis herausgestellt hat, das vor allem bei großen Software-Projekten durch die “Freiheit des Überschreibens von virtuellen Methoden” das Liskov-Substitutions-Prinzip stark gefährdet bzw. zum Teil durchbrochen wird.
Careful Virtual
Zu dieser Problemstellung drückt sich auch Anders Hejlsberg, leitender Architekt und Miterfinder der Programmiersprache von C# (Microsoft), besonders deutlich aus. In einem Interview über die C#-Design-Konzepte geht er indirekt auf die Probleme der Stabilität, der Gefährung des vereinbarten Verhaltens (“Incoming and outgoing contracts”) und Versionierungsproblemen durch virtuelle Methoden ein. Seiner Meinung nach ist durch generellen oder exzessiven Einsatz virtueller Methoden (wie z.B. in Java) die Gefahr wesentlich größer, das o.g. Probleme auftreten.
Ein Zitat von Anders Hejlsberg trifft meiner Meinung nach den Nagel auf den Kopf: > There are two schools of thought about virtual methods. The academic school of thought says, “Everything should be virtual, because I might want to override it someday.” The pragmatic school of thought, which comes from building real applications that run in the real world, says, “We’ve got to be real careful about what we make virtual.”
Ich persönlich schließe mich der Schule des “Lasst uns virtual nur dann verwenden, wenn es wirklich notwendig ist” an. Es ist vollkommen klar, das eine virtuelle Methode ein wichtiges Werkzeug für gutes OO-Design ist. Das wird alleine schon dadurch deutlich, das sie öfter zum Einsatz kommt, wenn man essentielle Design Patterns wie z.B. Template Method, Composite, Decorator oder Visitor (um nur einige zu nennen) implementiert.
Jedoch ist es meiner Meinung nach besonders wichtig zu erkennen, dass man virtuelle Methoden wirklich nur dann einsetzt, wenn es notwendig bzw. sinnvoll ist. Eine pauschale Deklaration einer virtuellen Methode mit der Begründung der Erweiterbarkeit ist unüberlegt und in den meisten Fällen nicht sinnvoll. Folgerichtig ist in meinen Augen jeder Entwickler mit Sicherheit gut beraten, die Microsoft Guidelines zum Einsatz virtueller Methoden zu beachten.
byblogring.orgonJanuary 10th 2009Blogring für pragmatics+definition…
Verwandte Blog-Einträge…