Objektorientiertes Programmieren mit Klassen

André Minhorst, Duisburg

VBA wird im Allgemeinen die Eigenschaft abgesprochen, eine objektorientierte Programmiersprache zu sein. Um diese Aussage zu untersuchen, müsste man erst einmal festlegen, ab wann eine Sprache objektorientiert ist und welche Eigenschaften für diese Bezeichnung vorhanden sein müssen. Lässt man einmal außen vor, dass Vererbung und Polymorphismus im VBA-Sprachgebrauch Fremdwörter sind, kann man VBA sicher als objektorientierte Sprache auffassen. Wie auch immer – im vorliegenden Beitrag erfahren Sie, wie Sie sich die objektorientierten Eigenschaften von VBA zu Nutze machen.

Wer mit Access arbeitet und dabei VBA für die Entwicklung von Datenbankanwendungen verwendet, kann vermutlich mit Objekten verschiedenen Typs umgehen – wenn er auch vielleicht noch nie einen eigenen Objekttyp erstellt hat.

Sicher hat jeder schon einmal ein Recordset via VBA geöffnet und auf die darin enthaltenen Methoden wie Open, MoveNext, AddNew, Update oder Close zugegriffen oder Informationen aus Eigenschaften wie RecordCount, EOF oder Filter verwendet. Beispiele dafür zeigt die Routine aus Quellcode 1, die eine auf der Tabelle tblKontakte basierende Datensatzgruppe öffnet und den Inhalt der einzelnen Datensätze ausgibt. Mit diesem Code erzeugt man unter anderem eine Instanz des Objekttyps Recordset, legt einige seiner Eigenschaften wie beispielsweise die Datenherkunft und die zugrunde liegende Verbindung fest und greift anschließend auf die so verfügbar gemachten Daten zu.

Public Sub OpenRecordset()
    Dim cnn As ADODB.Connection
    Dim rst As ADODB.Recordset
    Set cnn = CurrentProject.Connection
    Set rst = New ADODB.Recordset
    rst.Open "tblKontakte", cnn, adOpenKeyset, _        adLockPessimistic
    Do While Not rst.EOF
        Debug.Print rst!KontaktID, rst!Vorname, _            rst!Nachname
        rst.MoveNext
    Loop
    rst.Close
    Set rst = Nothing
    Set cnn = Nothing
End Sub

Quellcode 1

Eine weitere, ganz offensichtliche Objektart ist beispielsweise ein Formular – wie für ein Objekt üblich, verfügt es über Methoden, Eigenschaften und Ereignisse. Ein Formularobjekt kann wiederum Steuerelemente enthalten, die ebenfalls Objekttypen repräsentieren.

Mit den in Access vorhandenen Objekttypen lässt sich jede Menge nützlicher Dinge anstellen. Ein wichtiger Aspekt dabei ist, dass die unterschiedlichen Objekttypen bestimmte Methoden, Eigenschaften und Ereignisse enthalten. Ein Objekt des Typs Recordset fasst beispielsweise eine Menge Funktionalität zusammen. Und das Beste daran ist, dass man sich gar nicht darum kümmern muss, was im Innern dieses Objekts passiert – es reicht völlig aus, dass man die Methoden, Eigenschaften und Ereignisse kennt. Wen interessiert denn schon, was intern alles abläuft, wenn man die Methode AddNew eines Recordset-Objekts aufruft Wichtig ist allein das Kennen der Schnittstelle und dass das Objekt die gewünschten Reaktionen und Ergebnisse auf die getätigten Eingaben liefert.

Hinweis

Um keine Verwirrung bezüglich der hier verwendeten Begriffe aufkommen zu lassen, sollen folgende Definitionen gelten: Der Inhalt eines Klassenmoduls definiert eine Klasse. Eine Klasse wird auch Objekttyp genannt. Das Instanzieren einer Klasse beziehungsweise eines Objekttyps erzeugt ein Objekt. Auf das Objekt kann man in der Folge über die Objektvariable zugreifen. Das Klassenmodul enthält also einen Entwurf dessen, wie das Objekt sich verhalten soll. Dieser Entwurf besteht aus der Definition geeigneter Eigenschaften, Methoden und Ereignisse sowie der dahinter liegenden Funktionen.

Nun bietet Access nur eine begrenzte Menge Objekttypen, die allerdings bereits viele Anforderungen abdecken. Genau genommen sind Art und Menge der Objekttypen gerade so bemessen, dass sie als vernünftige Grundlage für den Aufbau der jeweils individuell abzubildenden Geschäftsprozesse einer Anwendung dienen.

Damit gibt es bereits einige gute Gründe, um eigene Objekttypen zu schaffen:

  • Objekttypen enthalten Methoden, Eigenschaften und Ereignisse und machen diese über eine leicht zugängliche Schnittstelle verfügbar.
  • Affinität: Mit der Definition eines Objekttyps macht man Softwareentwicklung wesentlich greifbarer, denn reelle Objekte wie beispielsweise der nachfolgend vorgestellte Kontakt können über realitätsnahe Attribute angesprochen werden.
  • Komplexität verbergen: Objekttypen verbergen mitunter komplexe Vorgänge, um die man sich, wenn sie einmal gestestet und praxiserprobt sind, keine Gedanken mehr machen muss. Der ansonsten offen liegende Code verschwindet in einer Black Box; nicht mehr die darin enthaltenen Techniken interessieren, sondern allein die Schnittstelle.
  • Wiederverwendbarkeit: Auf den Funktionsumfang eines Objekts kann man von der ganzen Anwendung aus zugreifen; unter Umständen können Objekttypen sogar in weiteren Anwendungen zum Einsatz kommen – beispielsweise, wenn die Klassen nicht an eine bestimmte Datenherkunft gebunden sind.
  • Weitergabe: Objekttypen können in Form von Klassenmodulen leicht weitergegeben werden; eine funktionierende Schnittstelle und eine brauchbare Dokumentation vorausgesetzt, können andere Entwickler diese leicht weiterverwenden.
  • Wie bereits oben erwähnt, ist einer der Vorteile der Verwendung von Objekttypen, dass man reelle Objekte und deren Eigenschaften in einer Einheit zusammenfassen kann, deren Methoden, Eigenschaften und Ereignisse über eine entsprechende Schnittstelle erreichbar sind. Unter VBA spricht man in diesem Zusammenhang von einem Klassenmodul.

    Als Beispiel für die Erläuterung des Aufbaus und der Verwendung von Objekttypen dient ein Kontakt. Er enthält bestimmte Informationen zu einer Person wie Vorname, Nachname, Geschlecht und Adressdaten. Um nicht nur die Verwendung von Eigenschaften, sondern auch den Einsatz von Methoden vorzustellen, erhält die Beispielobjektklasse zusätzlich eine Routine zur Ausgabe der Adressdaten in Form einer Anschrift.

    Hinweis

    Die zu den nachfolgenden Beispielen gehörenden Objekte und Codes finden Sie in der Beispieldatenbank OOMitAccess.mdb für Access 2000 und höher auf der beiliegenden CD.

    Erstellen eines Klassenmoduls

    Access stellt drei unterschiedliche Modularten zur Verfügung: die Klassenmodule von Formularen und Berichten, Standardmodule sowie Klassenmodule, die nicht an ein bestimmtes Objekt wie ein Formular oder einen Bericht gebunden sind. Die Klassenmodule von Formularen und Berichten sind prinzipiell mit den im Anschluss vorgestellten Klassenmodulen identisch; der einzige Unterschied ist, dass Letztere keine Oberfläche in Form eines Formulars oder Berichts enthalten.

    Um ein solches Klassenmodul zu erstellen, wählen Sie im VBA-Editor von Access den Menüeintrag Einfügen/Klassenmodul aus. Daraufhin öffnet Access ein fast leeres neues Klassenmodul, das Sie am besten direkt unter dem gewünschten Namen speichern – beispielsweise clsKontakt. Wählen Sie den Namen eines Klassenmoduls immer so, dass er auch verrät, welches Objekt sich dahinter verbirgt – später werden Sie über diesen Namen auf diese Klasse zugreifen.

    Praxis-Tipp

    Damit der Debugger sich meldet, wenn eine Variable nicht ordnungsgemäß deklariert ist, sollten Sie im Prozedurkopf die Anweisung Option Explicit hinzufügen.

    Schreib- und lesbare Variablen

    Die Eigenschaften eines Objekttyps speichert man in herkömmlicher Weise in Variablen. Die Zugriffsmöglichkeiten auf diese Variablen kann man allerdings wesentlich flexibler gestalten als in Prozeduren in Standardmodulen.

    Sie können eine Variable innerhalb eines Klassenmoduls natürlich als öffentlich zugänglich deklarieren, indem Sie etwa folgende Anweisung verwenden:

    Public Vorname As String

    Damit können Sie den Wert dieser Variablen von außen lesen und auch ändern, haben aber keinerlei Vorteile der in Klassenmodulen üblichen Art der Deklaration.

    Dort gibt es nämlich so genannte Property-Funktionen, über die man von außen den Wert einer Variablen lesen und ändern kann. Daher deklariert man Variablen in Klassenmodulen niemals als öffentlich, sondern immer als privat. Die Property-Funktionen erlauben nicht nur den schreibenden und lesenden Zugriff auf die privaten Variablen (wobei es für jede Zugriffsart eine eigene Funktion gibt), sondern man kann dort beliebige weitere Anweisungen unterbringen. Auf diese Weise lässt sich beispielsweise ein Zeiger setzen, der Informationen darüber enthält, ob sich eine Variable seit Erstellung der Objektinstanz geändert hat.

    Namenskonventionen

    Die Variablen, die über Property-Funktionen für die Außenwelt erreichbar sein sollen, kennzeichnet man durch Voranstellen eines weiteren Buchstabens zum eigentlichen Variablennamen. Genau genommen wählt man einen Namen aus, unter dem die Variable nach außen erscheinen soll, wie beispielsweise Vorname, und nennt die Variable intern mVorname.

    Hinzufügen einer Variablen

    Um eine bessere Vorstellung davon zu bekommen, was es mit diesen Property-Funktionen auf sich hat, fügen Sie der Klasse einfach eine Variable hinzu und erstellen zwei passende Property-Funktionen:

    Dim mVorname As String
    Public Property Get Vorname() As String
        Vorname = mVorname
    End Property
    Public Property Let Vorname(strVorname _    As String)
        mVorname = strVorname
    End Property

    Testen der Variablen

    Zum Testen der Funktionsweise verwenden Sie eine Prozedur namens BeispielKontakt in einem beliebigen Standardmodul. Um die Klasse verfügbar zu machen, deklarieren Sie zunächst eine entsprechende Objektvariable:

    Public Sub KontaktBeispiel()
        Dim objKontakt As clsKontakt
        ''... weitere Anweisungen
    End Sub

    Nach der Eingabe des Schlüsselwortes As erscheint die Liste aller verfügbaren Objekttypen, unter denen sich nun auch die neu erstellte Klasse befindet – wenn Sie also später mal eine ganze Menge eigener Objekttypen verwenden, müssen Sie sich noch nicht einmal mehr deren Namen genau merken.

    Anschließend erzeugen Sie eine Instanz dieses Objekttyps, die Sie in der Prozedur verwenden möchten:

    Set objKontakt = New clsKontakt

    Sie könnten die ersten beiden Anweisungen auch zu einer einzigen Anweisung zusammenfassen:

    Dim objKontakt As New clsKontakt

    Dadurch sparen Sie zwar eine Zeile, aber wenn Sie die Objektinstanz möglicherweise erst später benötigen oder es sich erst im Verlaufe der Prozedur herausstellt, ob Sie diese überhaupt brauchen, verschwenden Sie unter Umständen wertvolle Ressourcen.

    Damit auch alles seine Ordnung hat und Objekte nicht unnötig Speicherplatz belegen, obwohl sie nicht mehr benötigt werden, legen Sie vorsichtshalber jetzt schon die Anweisung zum Zerstören des Objektes an:

    Set objKontakt = Nothing

    Das ist zwar im vorliegenden Fall nicht unbedingt erforderlich, da das Objekt ohnehin nach Beenden der Prozedur zerstört wird, aber es ist programmiertechnisch sauberer. Die folgenden Beispielanweisungen fügen Sie natürlich vor dieser Anweisung ein, da sie sich sonst auf ein nicht mehr vorhandenes Objekt bezögen.

    Und nun geht”s an die Variable mVorname und deren Property-Prozeduren, die zusammen die les- und schreibbare Eigenschaft Vorname ergeben. Die folgende Anweisung setzt den Wert dieser Variablen auf den Wert Heinz:

    objKontakt.Vorname = "Heinz"

    Dank Intellisense erscheint der Eigenschaftsname direkt nach der Eingabe des Punktes hinter objKontakt in der Liste der verfügbaren Eigenschaften, Methoden und Ereignisse dieses Objekts – bisher also allein auf weiter Flur (s. Abb. 1).

    Abb. 1: Intellisense vereinfacht den Zugriff auf Objekteigenschaften.

    Um zu überprüfen, ob die Zuweisung funktioniert, geben Sie mit nachfolgender Anweisung den Inhalt der Eigenschaft im Testfenster aus:

    Debug.Print objKontakt.Vorname

    Was ist passiert

    über die Zuweisung der Zeichenkette an objKontakt.Vorname haben Sie die Property Let-Methode aufgerufen. Diese nimmt den übergebenen Wert über den Parameter strVorname entgegen und weist ihn der privaten Variablen mVorname zu.

    Bei der nachfolgenden Ausgabe läuft es umgekehrt: Die Propery Get-Prozedur ermittelt den Wert der Variablen mVorname und schickt ihn über die Eigenschaft Vorname zur Ausgabe.

    Hinweis

    Vielleicht haben Sie den großen Vorteil dieser Vorgehensweise schon erkannt: Sie können mit Hilfe von Get- und Let-Prozeduren für eine Property festlegen, ob diese zum Lesen, Schreiben oder gar für beide Zugriffsarten verfügbar sein soll.

    Im Gleichschritt: Marsch!

    Nachdem Sie sich von der Funktionsweise der Eigenschaft Vorname überzeugt haben, können Sie nun die Variablen für die weiteren Eigenschaften des Kontakts wie Nachname, Strasse, PLZ, Ort, Land und Unternehmen sowie die entsprechenden Property Let und Property Get-Prozeduren anlegen. Damit hätten Sie eine Klasse erzeugt, die wichtige Eigenschaften eines Kontaktes erhalten und wieder ausgeben kann.

    Die Ereignisse Initialize und Terminate

    Klassenmodule stellen zwei eingebaute Ereigniseigenschaften zur Verfügung, die beim Erzeugen und beim Zerstören einer Objektinstanz ausgelöst werden.

    Um die entsprechenden Ereigniseigenschaften zu erzeugen, wählen Sie einfach im VBA-Editor im linken Kombinationsfeld den Eintrag Class und im rechten den Namen der gewünschten Ereignisprozedur aus (s. Abb. 2).

    Abb. 2: Erzeugen einer Ereignisprozedur einer Klasse

    Diese Eigenschaften können Sie beispielsweise verwenden, um beim Erzeugen der Objektinstanz Variablen auf bestimmte Startwerte oder Verweise auf andere Objekte zu setzen. Beim Zerstören einer Objektinstanz erledigt man mit der entsprechenden Ereignisprozedur notwendige Aufräumarbeiten.

    Verwenden von Enumerationen

    Sicher kennen Sie die Möglichkeit, per Intellisense eine vordefinierte Konstante für einen Parameter auszuwählen. Das Gleiche ist auch mit den Eigenschaften einer Objektinstanz möglich. Um bei unserem Beispiel mit den Kontaktdaten zu bleiben, fügen wir diesem noch eine Eigenschaft namens Geschlecht hinzu. Dafür gibt es unter normalen Umständen nur zwei mögliche Einträge, deren Auswahl man dem Benutzer gerne abnimmt. Die passende Enumeration sieht folgendermaßen aus:

    Public Enum enumGeschlecht
        männlich
        weiblich
    End Enum

    Die Definition der entsprechenden Variablen sowie der Property Let- und Get-Prozeduren sieht wie folgt aus:

    Dim mGeschlecht As enumGeschlecht
    Public Property Get Geschlecht() As enumGeschlecht
        Geschlecht = mGeschlecht
    End Property
    Public Property Let Geschlecht(lngGeschlecht As enumGeschlecht)
        mGeschlecht = lngGeschlecht
    End Property

    Es gibt also prinzipiell keine Unterschiede zu herkömmlichen Datentypen, mit der Ausnahme, dass der Datentyp als Enumeration definiert wird.

    Hier sind zwei Dinge zu beachten: Beim Zugriff auf diese Objekteigenschaft stehen zwar die beiden Konstanten zur Verfügung (s. Abb. 3). Wenn man sich den ausgewählten Wert nachher ausgeben lässt, erhält man aber nicht den ausgewählten Ausdruck, sondern die dahinter stehende Konstante.

    Public Function Anschrift() As String
        Dim strAnschrift As String
        Dim strAnrede As String
        If mGeschlecht = männlich Then
            strAnrede = "Herrn "
        Else
            strAnrede = "Frau "
        End If
        If Not mUnternehmen = "" Then
            strAnschrift = strAnschrift & mUnternehmen & vbCrLf
            strAnschrift = strAnschrift & strAnrede & mVorname & " " & mNachname & vbCrLf
        Else
            strAnschrift = strAnschrift & strAnrede & vbCrLf
            strAnschrift = strAnschrift & mVorname & " " & strNachname & vbCrLf
        End If
        strAnschrift = strAnschrift & mStrasse & vbCrLf
        strAnschrift = strAnschrift & mPLZ & " " & mOrt & vbCrLf
        strAnschrift = strAnschrift & mLand
        Anschrift = strAnschrift
    End Function

    Quellcode 1

    Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

    den kompletten Artikel im PDF-Format mit Beispieldatenbank

    diesen und alle anderen Artikel mit dem Jahresabo

    Schreibe einen Kommentar