Wer bereits Erfahrungen mit Java, C++ oder C# gesammelt hat, vermisst beim Schreiben von Programmen in C mit Sicherheit viele bequeme Sprach-Features. GObject bietet die meisten Eigenschaften auch in C an. Es ist allerdings nur eine Bibliothek, was mit einigen Nachteilen, wie dem Schreiben von so genanntem Boilerplate-Code, verbunden ist. Mit Vala haben Sie die Möglichkeit, wie in anderen modernen objekt-orientierten Sprachen zu programmieren. Zusätzlich können Sie auf die praktischen Eigenschaften von "GObject" zurückgreifen, das Gemeinsamkeiten mit "java.Object" teilt. Einen ersten Überblick über Vala gibt der Artikel "GObject ohne Kopfschmerzen" sowie das Interview mit dem Vala-Erfinder Jürg Billeter.
Der vorliegende Artikel macht anhand einiger Codebeispiele wichtige Eigenschaften der High-Level-Sprache anschaulich. Die im weiteren Verlauf vorgestellten Quelltexte übersetzen Sie mit "valac --pkg glib-2.0 Vala-Datei -o Ausgabedatei". Falls Sie zusätzlich noch den Parameter "--save-temps" angeben, bietet sich Ihnen die Möglichkeit den von Vala generierten C-Code anzusehen. Er ist im Vergleich zu dem, was andere Compiler ausgeben, immer noch sehr lesbar und sorgt unter Umständen für einige "Aha-Erlebnisse". Eine Warnung vorweg: Generell ist der erzeugte C-Code enorm groß, seine Lektüre setzt vertieftes GObject-Wissen voraus.
Vererbung à la Vala
Klassen abzuleiten ist heute alltägliche Praxis. Die Syntax von Vala entspricht der von C++ und C#: "public class Unterklasse : Superklasse" bedeutet, dass "Unterklasse" von "Superklasse" alle Eigenschaften erbt. Beachten Sie, dass ein Weglassen des Schlüsselwortes "public" dazu führt, dass die Klasse privat ist. Gleiches gilt übrigens auch für Felder/Klassenvariablen. In Vala gibt es wegen der Affinität zu "GObject" noch eine Besonderheit: Um die Vorteile von "GObject" nutzen zu können, leiten Sie die allgemeinste Klasse direkt von "Object" ab, wie der folgende kurze Quelltext demonstriert (Listing 1):
| Listing 1: Einfache Klasse |
|---|
using GLib;
class Klasse : Object {
private string s;
private int i;
public void foo(string str, int i, Object obj) {
// do the foo
}
}
|
Die Anweisung "using" entspricht in Vala dem, was Sie als "import" von Java kennen. Ohne das "using"-Statement müssen Sie statt "Object", "GLib.Object" schreiben, was bei großen Programmen zu einem deutlichen Mehraufwand führt. Ein "#include", wie in C beziehungsweise C++ ist nicht notwendig. Im Allgemeinen ist es nicht Pflicht, von "Object" zu erben, aber es bringt die von "GObject" bekannten Vorteile mit.
Die Vererbung funktioniert in einer der nächsten Versionen auch mit Strukturen ("struct"). Aufgrund der fehlenden Implementierung im Compiler müssen Sie momentan noch darauf verzichten.
Schnittstellen-Technik und Abstraktes
Vala erlaubt keine Mehrfachvererbung, wie diese in C++ oder Python existiert. Dies hat viele Vorteile (Stichwort "Method Resolution Order") aber auch Nachteile. Der Ausweg daraus bedeutet die Einführung von Schnittstellen -- bekannt aus Java und C#. Klassen implementieren mehrere Interfaces und erben nur von einer Superklasse, was auch in Vala der Fall ist. Auch hier gibt es wieder eine kleine Besonderheit: Falls eine Klasse ein Interface implementiert, das seinerseits die Eigenschaften von "GObject" oder "GtkWidget" übernimmt, ist es notwendig auch diese Klasse beispielsweise von "Object" oder "Gtk.Widget" abzuleiten (Listing 2):
| Listing 2: Interfaces und abstrakte Klassen |
|---|
using GLib;
public interface MyInterface : Object {
...
}
private class MyClass : Object, MyInterface {
...
}
|
Der Grund ist einerseits, dass Sie den Typen angeben, von dem Sie auch erben möchten, oder bei mehreren Interfaces der Compiler keine Informationen darüber hat, was die Basisklasse ist. Dieses Vorgehen vermeidet daher Fehler im weiteren Übersetzungsverlauf. Es gibt noch zwei weitere Unterschiede zu C# und Java: Schnittstellen dürfen konkrete Methoden enthalten, was aus "GObject" hervorgeht. Methoden, die es zu implementieren gilt, benötigen das "abstract"-Schlüsselwort.
Abstrakte Klassen bilden eine Schnittmenge von Interfaces und Klassen: Sie enthalten sowohl abstrakte Methoden, die Sie in einer Unterklasse implementieren müssen und "normale" Methoden. Letztere können Sie selbstverständlich auch in Vala überschreiben: Dazu ist die Methode in der Superklasse mit dem Schlüsselwort "virtual" und in der Unterklasse mit "override" zu versehen. Das folgende Listing gibt einen sehr genauen Überblick (Listing 3):
| Listing 3: Methoden überschreiben |
|---|
using GLib;
public interface IFace : Object {
protected abstract void foo();
protected void bar() {
stdout.printf("IFace.bar\n");
}
}
public abstract class AKlasse : Object, IFace {
protected virtual void foo() {
stdout.printf("AKlasse.foo\n");
}
protected virtual void bar() {
stdout.printf("AKlasse.bar\n");
}
protected abstract void foobar();
}
public class ImplKlasse : AKlasse {
protected override void foo() {
stdout.printf("ImplKlasse.foo\n");
}
protected override void bar() {
stdout.printf("ImplKlasse.bar\n");
}
protected override void foobar() {
stdout.printf("ImplKlasse.foobar\n");
}
}
|
Wie Sie eventuell schon bemerkt haben, gibt es im Interface das Schlüsselwort "virtual" noch nicht, da der Compiler damit nicht umgehen kann. Ein "override" in der Implementierungsklasse oder einer weiteren Unterklasse führt im Augenblick noch zu einer Fehlermeldung. Wenn Sie sich an die etwas von Java oder C# abweichende Syntax und Semantik gewohnt haben, bereitet Ihnen das in Zukunft keine Schwierigkeiten mehr.
