FoxPro

FoxPro Developer's Conference '94

 

Session 133
Session 232
Einführung in die Objektorientierte Programmierung

Jürgen Wondzinski
ProLib GmbH


Von Prozedural nach Objekt-Orientiert

Haben Sie jemals die fünf Ausgaben der vierbändigen Trilogie „Per Anhalter duch die Galaxis" von Douglas Adams gelesen? Na, dann wissen Sie schon ungefähr, was Sie auf den folgenden Seiten erwartet… (Wenn Sie dieses Meisterstück der absurden Science-Fiction wirklich noch nicht kennen, dann sollten Sie es aber schnellstens nachholen!) Aber genau so wie dem Helden jener Geschichte geht es wohl den meisten FoxProProgrammierern (und sicher auch anderen) heute:

Jahrelang war man in seiner vertrauten Umgebung zuhause, die Programme gingen ihren gewohnten Gang, und die (DOS-) Welt war im Großen und Ganzen in Ordnung. Man hatte seinen eigenen Stil entwickelt, der Programmfluß war wohlgeordnet vom Start übers Menü, durch die einzelnen Unterprogramme hindurch, bis hin zur Abschlußroutine. Doch dann kam eines Tages eine gewaltige Macht, und ihr Herrscher (zwar noch jung, dafür aber nervös an der Brille rückend) verkündete, daß diese vertraute Welt nichts mehr tauge, dem Fortschritt im Wege stehe und daher vernichtet wird. Tja, und da stehen Sie nun, auf dem Sprung in die weite Welt voller neuer Möglichkeiten und Gefahren. Ihre Programme sollen nun Objekte sein, die losgelöst jeder Fessel durch die bunte Windows-Welt schwimmen. Vorbei die Zeit der klaren Abläufe; der Anwender bestimmt nun, und ihr Programm hat nur noch zu reagieren.

Im Folgenden will ich Ihnen helfen, diesen Wandel so einfach wie möglich durchzumachen. Sicher ist Ihnen manches, was Sie hier lesen werden, schon bekannt, denn FoxPro hat ja schon seit der Version 2.0 (anno 1991) viele Ideen der objektorientierten Programmierung ansatzweise verwirklicht. So gesehen, fehlt uns nicht mehr recht viel: ein paar neue Befehle hier, ein etwas anderer Ablauf da, und fertig ist FoxPro 3 !


 

OOP: Die neue Geheimsprache

Wenn Sie heutzutage auf einem ganz normalen Programmierertreffen einigermaßen heil überleben wollen, dann müssen Sie nur ganze zehn Worte beherrschen: Objekte, Methoden, Nachrichten, Klassen, Unterklassen, Instanzen, Vererbung, Kapselung, Abstraktion und für Überflieger: Polymorphismus. Wenn Sie es ganz doll treiben wollen, dann verwenden Sie einfach noch die englischen Begriffe wie: inheritance oder encapsulation. Vielleicht üben Sie das heute abend schon mal beim Duschen, dann haben Sie beim nächsten Treffen genügend Bewunderer…


 

OOP Warum nur ?

Bevor wir uns nun in diese „Schöne Neue Welt" stürzen und erforschen, was diese tollen Worte tatsächlich zu bedeuten haben, sollten wir vielleicht noch kurz die jetztige Programmierwelt kritisch betrachten. Schon was von dem Schlagwort der sog. Softwarekrise gehört? Soll heißen: Programme werden nur mit Verspätung fertig, kosten mehr als geplant und haben diverse Fehler.

Zusammengefaßt läßt sich unser Beruf (und unser Problem) sicher so darstellen: Unsere Anwender sammeln immer mehr Daten, die sie immer besser auswerten und verarbeiten wollen. Noch während wir an den dazu notwendigen Programmen schreiben, hat der Kunde schon wieder ganz andere Anforderungen und Wünsche. (Ist ja auch sein gutes Recht, wir wären die Letzten, die ihm diese Wünsche verübeln würden; schließlich leben wir ja davon. Aber muß es denn so schnell sein?) Während wir noch am Testen und Fehlerbeseitigen sind, liegen schon die nächsten Wunschzettel auf dem Tisch. Tatsache ist, daß man mit den althergebrachten Programmiermethoden den sich ständig ändernden Anforderungen der heutigen Zeit nicht mehr Herr wird. Dauert doch das Erstellen eines Pflichtenheftes meist schon länger als mancher Abteilungsleiter dafür verantwortlich ist!


 

Die Software-Krise und RAD

Die Lösung dieses Teufelskreises heißt RAD, oder auch Rapid Application Development. Darunter versteht man ganz allgemein das schnelle Erstellen eines Prototypen, am besten im Beisein des Auftraggebers, denn nur in Zusammenarbeit mit dem Anwender kann man schon in der Planungsphase grundlegende Denkfehler und Probleme umgehen. Aus diesem Prototypen wird wiederum durch Verfeinern und Erweitern das Endprodukt; d.h. der Prototyp ist nicht mehr nur Muster, sondern der Kern des Ganzen.

Dieses Verfahren bedingt fertige Bausteine, die schnell und einfach zu einer Lösung zusammengesetzt werden können. Man braucht erprobte, in sich geschlossene Einheiten, die speziell zur Lösung einer bestimmten Aufgabe entwickelt worden sind. Andererseits sollen aber diese definierten Abläufe jederzeit einfach an spezielle Anforderungen anpaßbar sein. Mit der üblichen „Codiererei" kommen wir hier nicht mehr ans Ziel.

Nehmen wir mal einen bildlichen Vergleich her, damit Ihnen die Einsicht leichter fällt:

Angenommen, Sie wollen ein Haus bauen. Am einfachsten geht das, indem Sie alles selber machen, denn immerhin ist’s ja ihr Haus, und Sie wissen, wie es werden soll. Also lernen Sie mauern, schweißen, malen, schreinern usw. Dabei wird zwar ab und an der Daumen dran glauben müssen, und manche Wand wird etwas krumm sein, aber es ist eben ihr Haus. Unterm Strich haben Sie dafür zwei Jahre benötigt, aber was solls. Und daß bei der Klospülung ausnahmsweise dampfendes Wasser kommt, wird von Ihnen der Einfachheit halber als besonderes „Feature" und nicht als „Bug" bezeichnet… Wesentlich besser wäre es allerdings gewesen, wenn Sie z.B. die Wasserrohre vom Installateur hätten legen lassen, oder die Fenster gleich fertig beim Schreiner bestellt hätten.

Der Stand der Softwareproduktion heute gleicht diesem Vorgehen. Anstatt Teilbereiche mit fertigen Spezialmodulen abzudecken, versuchen wir alles selbst zu machen, auch wenn wir uns erst in Teilbereiche langwierig einarbeiten müssen. Themen wie Serielle Kommunikation, Barcodes, Electronic Banking usw sind sicherlich Gebiete, die einiges an Erfahrung voraussetzen. Unser Beruf wandelt sich immer mehr zum Problemlöser, zum Architekten. Wir bekommen eine Aufgabe und versuchen diese mit soviel Standardbausteinen wie möglich zu realisieren. Dabei interessiert es uns weniger, wie diese Bausteine intern funktionieren, wichtig ist, daß sie fehlerfrei und stabil sind. Diese Grundidee ist der Ursprung der objektorientierten Programmierung (Na endlich, nun wir sind wieder beim Thema…)


 

FoxPro 2.x und objektorientiert ?!

Und nun aber rein in die Praxis: Mit etwas Aufwand kann man viele Eigenschaften der objektorientierten Programmierung auch jetzt schon in FoxPro 2.x verwirklichen. Und je mehr Sie davon schon im Code verwenden, um so leichter wird später das Umsetzen.

Wie programmieren Sie heute? Klar, das Buch „Befehle und Funktionen" liegt mittenmang auf dem Schreibtisch (vermutlich steht der Monitor drauf, weil’s so schön dick ist); nein, das eigentliche Programmieren ist sicher nicht das Problem. Wichtiger ist das Organisieren der Programme, das Aufsplitten in möglichst kleine Teilbereiche. In der Fachliteratur als Reusable Code bezeichnet, ist es nichts anderes, als der Versuch, seinen Funktionen, Prozeduren und Unterprogrammen den Grünen Punkt aufzukleben und sie mehrmals zu verwenden. Dieses Wiederverwenden kann sich auf ein komplettes Aufgabengebiet beziehen (z.B ein Modul zum Erstellen von elektronischen Banküberweisungen) oder auch auf simple Grundfunktionen. Sicher haben Sie auch schon ein Programm mit dem schönen Namen HINWEIS(), ABFRAGE(), oder ähnlichem erstellt, mit dem Sie das tägliche Leben des Anwenders versüßen:

Genau das ist schon ein guter Ansatz: Eine Funktion, sauber ausgetestet, immer wieder zu gebrauchen. Voilá, schon haben wir unser erstes Objekt. Es hat eine spezifische Aufgabe (Frage anzeigen, Eingabe zurückgeben), dazu hat es interne Anweisungen, wie dies abzulaufen hat (Fenster erstellen, Text anzeigen, Eingabe validieren), und interne Informationen, (z.B. wo und in welcher Farbe das Fenster steht). Grob gesprochen, begegnen wir hier schon dem Begriff Methoden. Netterweise werden wenigstens die Informationen noch, wie gelernt, als Variablen bezeichnet.

Wichtig ist auch, daß eine Funktion absolut unabhängig von der Umgebung läuft, egal welche Dateien oder Variablen verwendet werden, die Funktion muß eine sog. BlackBox sein. Das Verhalten darf nur durch die übergebenen Informationen gesteuert werden. Siehe da, klingt verdächtig nach Kapselung! Aber wehe Ihnen, wenn sie innerhalb einer Funktion auf globale Variablen Bezug nehmen, denn schon ist damit eine Vorbedingung gestellt, die das einfache Wiederverwenden in anderen Applikationen in Frage stellt.

Hier bekommen wir nun die ersten Limitierungen unser jetzigen Spracheigenschaften zu spüren: Wir können z.B. unsere Variablen zur Zeit nicht hundertprozentig abschotten. In anderen Sprachen gibt es die sog. LOCAL Variableneigenschaft, die dafür sorgt, daß die so definierte Variable wirklich nur in der eigenen Funktion sichtbar ist. In unserer FoxPro Welt haben wir zwar die Eigenschaft PRIVATE, nur hat diese die unschöne Eigenschaft, daß die Abschottung damit nur nach oben hin machbar ist. Für von dieser Funktion aus aufgerufene Unterfunktionen sind die Variablen weiterhin sicht- und änderbar.

Einziger Ausweg aus dieser Misere ist Disziplin. Eiserne Regel für saubere Funktionen: Als erste Anweisung hat in jeder Routine ein

zu stehen und jede lokal verwendete Variable muß auch mit J beginnen. So kann keine Funktion aus Versehen die Variablen einer anderen Funktion überschreiben. Diese Namenskonventionen sind im FoxPro-Bereich hauptsächlich durch Alan Griver bekannt geworden, der sie in seinen „FLASH Technical Standards" dokumentiert hat.

1.Stelle Bedeutung   2.Stelle Bedeutung
p Local / (Private)   c Zeichen
g Global / (Public)   n Numerisch
t Parameter   l Logisch
j Junk / Schrott   d Datum
w Fenstername   m Memo
      a Array
      h Schaltfläche
      b Menü-Bar

Eine Variable sieht also dann etwa so aus: jcVorname oder gnUmsatz. Auf diese Weise haben Sie zwei Fliegen mit einer Klappe erschlagen: Zum einen können Sie über PRIVATE ALL LIKE p* alle Variablen, die mit p anfangen, zu Privates erklären, zum andern sollte es Ihnen nicht mehr passieren, daß sie die Variable gcArtikel mit einem numerischen Wert zuweisen, und zur Laufzeit dann mit einer Fehlermeldung „Datentyp stimmt nicht" beglückt werden. Sicher, diese Namenskonvention ersetzt in keiner Weise die echte Typisierung anderer Sprachen, aber man muß eben mit den gegebenen Möglichkeiten leben.

Ähnlich wird mit den Datenbankfeldern verfahren:

1.Stelle Bedeutung
c Zeichen
n Numerisch
d Datum
l Logik
m Memo
b Blob (General)

Haben Sie’s bemerkt? Die erste Stelle bei Variablen und bei Feldern kann sich nun nicht mehr überschneiden; der gefürchtete Effekt, aus Versehen statt einer Variablen den Feldinhalt zu verwenden, ist somit ausgeschlossen. Erstaunlich, was so ein bischen Disziplin bewirken kann!


 

Vererbung und Instanzen in FoxPro

Auch das Thema Vererbung und Instanzen kann man ansatzweise schon in FoxPro nachvollziehen. Bevor nun die C-Freaks aufheulen: Natürlich können wir nicht ein reines OOP System hier nachvollziehen, aber (wie schon weiter oben gesagt) die Ansätze sind vorhanden, und durch das Testen mit bekannten Methoden kann man die neuen Ideen wesentlich einfacher verstehen.

Haben Sie sich schon mal Gedanken gemacht, wie FoxPro Ihre Prozeduren findet? Angenommen, sie haben die folgende Konstruktion:

Klar? Wir haben ein Hauptprogramm, das eine Prozedurdatei setzt und danach eine Maske anstartet. Die Maske hat ein Eingabefeld, und dieses VALID ruft nun die Procedur Test auf. Nur: dummerweise ist TEST in vier Stellen vorhanden: In der Maske, im Hauptprogramm, in der Prozedurdatei und als Einzelprogramm. Preisfrage: Welche startet? Ganz klar: Die im aktuellen Modul, also erhalten wir die Nachricht "In Maske".

FoxPro geht beim Suchen folgendermaßen vor:

Diese Eigenschaft können Sie sich in wunderbarer Weise zu nutze machen: Allgemeine generische Routinen kommen in die Prozedurdatei. Dadurch ist diese Funktion global verfügbar. Sie kennen die üblichen "Vor/Zurück/Erster/Letzer" Schaltflächen sicherlich. Wie oft haben Sie den dazugehörigen "SKIP.. "Code schon geschrieben? In jeder Maske wieder, geben Sie´s zu! Mit unserem Wissen können Sie nun diesen Code in die zentrale Prozedurdatei packen, und einfach von jeder Schaltflächenleiste aufrufen. Schlicht und ergreifend: Das ist Inheritance (Vererbung). Klingt zu einfach, oder? Ist aber genau das, was die Definition verlangt. Falls Sie einmal der Meinung sind, daß das Blättern anders zu erfolgen habe, dann ändern Sie das in ihrer einzigen Prozedur, und zur selben Zeit haben alle Programme, die diese Funktion einsetzen, diese neue Eigenschaften. Ersetzen sie die simple Blätter Funktion durch einen kompletten Maskenteil, dann kommen wir einem Objekt schon recht nahe. Andere Beispiele zum Vererben: Eine generelle Datensatz-Speichern Routine, eine Lösch-Routine, eine Neuanlagen-Routine. Allesamt generisch und für alle Programme einsetzbar.

Wenn Sie eine spezielle angepasste Version benötigen, dann setzen Sie in den CleanUp Teil der aktuellen Maske eine neue Funktion mit demselben Namen rein, und schon wird diese anstelle der generischen ausgeführt. An dem Schaltflächen Teil haben Sie keinerlei Änderung vorgenommen. Wir haben so die allgemeinen Regeln mit speziellen Regeln komplett übersteuert. Durch die Limitierungen FoxPro's müssen wir hierzu noch den kompletten Code duplizieren, auch wenn nur ein Teil anders ist. (hier geht uns also die Puste aus).

Aber noch nicht genug: Wenn wir zwar am Standardverhalten einer Routine nichts ändern moöchten, aber zusätzlich noch eine weitere Funktion ausführen müssen (z.B. eine Protokolldatei beim Speichern/Löschen mitführen), dann erstellen sie wiederum im aktuellen Programm die bewusste Funktion (die dadurch anstelle der generischen Funktion aufgerufen wird), und rufen nun erst die original generische Funktion auf. Dies können Sie mit dem Befehl

Auf diese Weise können Sie ein weiteres OOP Verhalten nachahmen: die Erweiterung ohne Veränderung bestehender Funktionen. Sie haben eine Subklasse erstellt.


 

Nun aber echte Objekte, bitte !

Nach diesem Ausflug in die grausame Welt der rauhen xBase Wirklichkeit wollen wir uns nun wieder etwas mehr in der Theorie weiterbewegen.

Da zum jetzigen Zeitpunkt die zukünftige FoxPro Version noch geheim ist, kan ich hier natürlich nicht die neuen Sprachkonstrukte verwenden. Die Codesprache ist also rein fiktiv, auf Grundlage von VisualBasic und C. Ken Levy hat in einem Artikel in FoxTalk (10/94) ein sehr anschauliches Codebeispiel veröffentlicht, das ich Ihnen hier der Einfachheit halber zeige

Ausgehend von unserem obigen Jetztzeit Modell, möchte ich Ihnen nun die einzelnen Begriffe definieren.

Zurückkommend auf das Stichwort RAD, ist das Grundprinzip das Zerlegen eines Problems in viele kleine Einzelkomponenten, deren Ablauf exakt beschrieben wird. Durch das Zerlegen auf viele Einzelkomponenten können Sie in der Summe mit weniger Code auskommen, da sich Grundmodule immer wieder verwenden lassen, und nur noch die Änderungen zum Standard zugefügt werden müssen. Nennen wir diese Grundmodule einfach OBJEKTE.

Eine Klasse ist sozusagen die Theorie, die Beschreibung eines Objektes. In einer Klasse sind alle Eigenschaften und das Verhalten definiert. Für unsere BLÄTTERN Schaltleiste, hoppla Blättern-OBJEKT, bedeutet dies, daß hier z.B. definiert ist:

Wie sie sehen, ist das ziemlich vertrauter Code. Eine Klasse ist im weitesten Sinne eigentlich ein Programm, mit Variablenzuweisungen und Funktionen drin. Nur eben komplett gekapselt. Über den "Alias" THIS wird immer auf das aktuelle Obekt Bezug genommen, ohne daß man dessen Namen wissen muß (Vergleichbar der Funktion PROGRAM(), die innerhalb eines Programms dessen Namen zurückgibt)

Dies ist eine neue Klasse, die sich auf eine andere Klasse bezieht und einige Änderungen vornimmt. Dies könnten z.B. neue oder geänderte Properties und Methoden sein. Diesen Vorgang nennt man auch Inheritance oder schön deutsch Vererbung. Die Subklasse erbt sozusagen die Eigenschaften des Vaters (oder der Mutter? Heißt ja eigentlich die Klasse...)

Änderungen an der übergeordneten Klasse (der sog SUPERCLASS) wirken sich natürlich auf die abgeleiteten SubClasses aus.

Hier wurde also ein zweites Fenster designed, das sich nur in der Breite und Titel vom ersten Fenster unterscheided. Sie sehen hier ganz deutlich die Code-Einsparung!

Erst durch das Instanzieren wird aus einer Klasse ein Objekt, mit dem man tatsächlich arbeiten kann. Das Objekt übernimmt also die theoretische Beschreibung der Klasse.

Von einer Klasse können (theoretisch) beliebig viele Objekte erstellt werden (zumindest bis der Hauptspeicher platzt). Dies sind dann die multiplen Instanzen. Man könnt´s auch Mehrfach-Aufruf nennen.. Alle diese Objekte sind erst mal eineiige Zwillinge mit den selben Eigenschaften und Methoden. Durch gezieltes Zuweisen von neuen Werten kann man nun von aussen Einfluß auf das Objekt nehmen.

Und natürlich kann man auch die Funktionen, ehh: Methoden eines Objektes aufrufen:

Bei der Erstellung neuer Klassen werden zurest einmal alle Methoden und Properties (Funktionen und Variablen) der beerbten Klasse übergeben. In der neuen Subklasse kann man nun gezielt die Punkte abändern, die anders sind: Durch Neudefinition einer Methode wird die Originalmethode übersteuert (überschreibende Vererbung genannt), oder durch zusätzliche Methoden/Properties wird es eine Erweiternde Vererbung.

In unserer Klasse ZweitesFenster könnten wir z.B. ändern:

Dies ist ein schönes Wort für die Abschottung der Internas eines Objektes. Nur über den Objektnamen ist an die internen Properties ranzukommen.

Eine Steigerung davon sind die PROTECTED PROPERTIES, deren Werte überhaupt nicht von Außen sichtbar oder beinflußbar sind.

Tja, da wird´s in der deutschen Sprache schwierig: Vielgesichtigkeit sagt mein allwissendes Nachschlagewerk... Bleiben wir also lieber beim Original.

In jeder Klassendefinition gibt es sog. Standard-Methoden, die eigentlich jede Klasse, jedes Objekt besitzt. Durch den Befehl <Objekt>.SHOW kann man mit einem generischen Befehl z.B. jedes Objekt zum Anzeigen bewegen. Wie und was genau in der SHOW Funktion dann abläuft, ist, kann total unterschiedlich sein. Also: Ein Befehl - Viele Wirkungen: Polymorphismus.

So, das war nun der erste Versuch, Ihnen die Niederungen der Objekte ein wenig näher zu bringen. Es gibt noch viele weitere Eigenschaften, die die Zukunft FoxPro's sehr interessant machen werden. Mit in dieses Gebäude der immer wieder verwendbaren Grundmodule gehört z.B. auch die OLE Technik, aber dies ist eine andere Geschichte bzw. Vortrag....


Einführung in die Objekt-orientierte Programmierung
(c)1994 Jurgen Wondzinski