Session D-KOMP

Komponenten/
Assembling

Manfred Rätzmann


Das Dilemma - ein Erlebnisbericht

Visual FoxPro ist seit über einem Jahr verfügbar. Nach dem ersten Kennenlernen war mir klar, daß ich neue Projekte nur mit VFP angehen wollte. Also wurde fleißig ausprobiert und abends statt des Krimis ein Fachbuch über Objektorientierung mit in’s Bett genommen. Die beinahe erste Erkenntnis beim Lesen war, daß die Götter der OO-Welt vor die Programmierung das Design gesetzt hatten. Eigentlich nichts Neues, man macht sich natürlich Gedanken und konzipiert sein Programm, bevor man mit der Entwicklung beginnt. Aber jetzt sollte zunächst ein komplettes, in sich stimmiges Objektmodell her. Woher nehmen? Zeitmangel und fehlende Erfahrungen brachten mich auf den Gedanken, eines der für Visual FoxPro angekündigten Frameworks zu erwerben. Aber ach: Keines ist bis heute so richtig über’s Beta-Stadium hinaus. Erfahrungen, die ich mit einem Framework für FPW 2.6 sammeln durfte (auf dessen Update zu Visual FoxPro ich heute noch warte) sowie die immer mal wieder aufbrandende Diskussion über Performance- und andere Probleme brachten mich von der Framework-Idee wieder weg. Also was tun? Kundenaufträge lagen vor, die mit Visual FoxPro realisiert werden sollten. Da hieß es: anfangen, das bisher Gelernte soweit möglich berücksichtigen und sehen, was draus wird.

Aus der Not eine Tugend machen

Ziemlich bald wurde mir klar, daß sich aus dieser Not auch eine Tugend machen läßt. Meine Angewohnheit, Problemstellungen möglichst allgemein zu sehen und zu lösen, führte dazu, daß sich im Laufe der Zeit die ersten, generell wiederverwendbaren Klassen ansammelten, was die Entwicklungsdauer der neueren Projekte schon spürbar reduzierte. Die Idee, die ich jetzt verfolge, und die ich Ihnen in dieser Session nahebringen will, ist, Anwendungsentwicklung als den Zusammenbau vorgefertigter Komponenten zu begreifen. Dabei sehe ich Visual FoxPro als mein Framework an, auf dessen Basis ich eigenständige, streng gekapselte Komponenten erstelle. Aus diesen Komponenten baue ich meine Applikationen zusammen. Was noch nicht als Komponente verfügbar ist, wird bei Gelegenheit dem Baukasten hinzugefügt. Mit Interesse habe ich bei der Installation von VFP 5.0 den Hinweis auf „New Wizzards“ gelesen: „Create a Framework for your application and add prebuilt components instantly with the Application Wizzard“. Offensichtlich wird diese Vorgehensweise auch von den VFP-Entwicklern unterstützt.

Was sind Komponenten?

Schrauben Sie Ihren Computer auf und schauen Sie sich sein Innenleben an. Jeder heute gebaute Computer besteht aus vorgefertigten Komponenten. Kein Hersteller kann es sich mehr leisten, vom Gehäuse bis zum Prozessor alles selbst zu fertigen. Die Einzelteile Ihres Computers, wie Netzteil, CD-ROM Laufwerk oder Motherboard sind selbst wieder aus Komponenten zusammengesetzt, aus elektronischen Bauteilen, die im Laden zu kaufen sind und aus spezialisierteren Teilen, in denen das Know-How des jeweiligen Herstellers steckt. Dieses Bild der Hardware ist meine Idealvorstellung einer Applikation. Warum sollte es nicht möglich sein, Software ähnlich aufzubauen? Einige fix und fertig zusammengeschraubte Komponenten kann man in Form von OCXen käuflich erwerben, andere Komponenten werden von Entwicklerkollegen als Public Domain zur Verfügung gestellt, anderes, besonders die Kleinteile, muß man einmal selbst entwickeln. Was bei dieser Entwicklung von eigenen Komponenten zu beachten ist, steht weiter unten.

Komponenten-Assembling oder Framework

Erich Gamma stellt in seinem Buch „Design Patterns“ Vererbung und Object-Komposition gegenüber und gelangt zu dem Prinzip: „Favor object composition over class inheritance“ (Bevorzuge Object-Composition gegenüber Vererbung). Ich meine, dieses Prinzip gilt auch für die Erstellung von Applikationen und ebenso für die Struktur der dafür benutzten Werkzeuge. Klassische Frameworks sind dagegen oftmals stark auf dem Vererbungsprinzip aufgebaut. Dabei entstehen häufig starke Kopplungen zwischen einzelnen Klassen. Dabei beziehen sich oft schon die Basisklassen aufeinander, weil in Methoden der Basisklassen auf Eigenschaften anderer Klassen zugegriffen wird. Die Eigenschaft wird als vorhanden vorausgesetzt, weil beide Klassen ja zum selben Framework gehören. Das führt dann im Extremfall dazu, daß ein simpler „Schließen“-Knopf nur mit den Framework-eigenen Forms funktioniert. Da alle diese Beziehungen untereinander ständig berücksichtigt werden müssen, wird der Aufbau eines in sich stimmigen Frameworks schnell eine äußerst komplexe Angelegenheit. Das Hauptziel des objektorientierten Designs - Komplexität zu kapseln und dadurch handhabbar zu machen - wird durch die dem Framework eigene Komplexität konterkariert.

Komponenten-Assembling hingegen bedeutet, voneinander unabhängige Komponenten zu einem Ganzen zu verbinden. Das stellt uns vor zwei Aufgaben:

  1. beim Entwurf und der Realisierung unserer Komponenten auf größtmögliche Unabhängigkeit zu achten und
  2. eine Verbindungstechnik zu entwicklen, die es ermöglicht, solche Komponenten in einer Applikation zusammenarbeiten zu lassen.

Komponenten werden als Klassen realisiert

Ich benutze den Begriff Komponenten, um den inhaltlichen Aspekt hervorzuheben. Natürlich sind die Komponenten eines Programms zur Laufzeit Objekte wie andere Objekte auch, deren Baupläne in Klassendefinitionen hinterlegt sind. Aber beim Komponentengedanken kommt es nicht so sehr auf die mehr technische Differenzierung zwischen Klasse und Objekt an. Komponenten müssen vor allem:

Die Vererbungshierarchie von Komponenten ist im allgemeinen flach. Sie enthält eventuell zwei, drei Varianten der Komponente, die auf einer gemeinsamen Elternklasse basieren. So gibt es in meinem Baukasten zwei Arten von Picklists (Forms, die den Inhalt einer Tabelle anzeigen und einen Satz auswählbar machen), nämlich an eine Form gebundene und ungebundene Picklists. Beide basieren auf der Elternklasse „Picklist“, die wiederum direkt auf FORM bzw. auf meiner Basisklassen-Form basiert.

Rahmenbedingungen

Den Rahmen bildet Visual FoxPro selbst. Damit haben wir einen kleinsten gemeinsamen Nenner, der vor allem wichtig ist, wenn Sie daran denken, Komponenten anderer Entwickler einzusetzen oder Ihre eigenen Komponenten an andere weiterzugeben. Wenn Sie allgemein einsetzbare Komponenten erschaffen wollen, können Sie sich nur auf das verlassen, was Visual FoxPro selbst zur Verfügung stellt. Jede Funktionalität, die Ihre Komponente zum reibungslosen Einsatz braucht, muß in der Komponente selbst enthalten sein. Sie dürfen eine solche Funktionalität auch nicht in einer Ihrer Basisklassen oder in einer Procedure-Datei ablegen, da Ihre Komponente sonst nur mit genau dieser Basisklasse oder dieser Procedure-Datei zusammen funktionieren würde. Entwicklen und testen Sie Ihre Komponente am besten als Ableitung einer Visual FoxPro Basisklasse. Erst die fertige Komponenten definieren Sie mit Hilfe des Klassenbrowsers um, sodaß Sie nun von einer der Basisklassen abgeleitet wird, die Sie in Ihren Projekten benutzen. So können Sie bei der Entwicklung der Komponente sicher sein, daß Sie keine Funktionalitäten Ihrer speziellen Basisklasse unbewußt vorausgesetzt haben.

Dokumentation

Dokumentieren Sie Ihre Komponenten! So wie Hardware-Bauteile in technischen Merkblättern beschrieben sind, muß für Software-Komponenten eines Dokumentation des Interfaces existieren. Ein solches technisches Merkblatt sollten Sie auch anfertigen, wenn Sie nicht beabsichtigen, Ihre Komponente weiterzugeben. Sie erleichtern sich damit selbst das Leben. Wie jeder Entwickler weiß, steht man nach einem Jahr seinen eigenen Programmen fast wie ein Fremder gegenüber. Dies gilt natürlich auch für die Schnittstellendefinition einer selbst entworfenen Komponente.

Eine Software-Komponente ist vollständig durch ihre offen gelegten (public) Eigenschaften und Methoden beschrieben. Deshalb sollte sich auch die Dokumentation darauf beschränken. Natürlich sollen Sie die internen Abläufe und Zusammenhänge im Sourcecode so genau wie nötig beschreiben. Im technischen Merkblatt Ihrer Komponente haben diese Interna jedoch nichts zu suchen. Hier sollten Sie nur das Verhalten der Komponente nach außen hin beschreiben.

Komponentendesign

Wie finden Sie mögliche Komponenten?

Komponenten ergeben sich aus generalisierbaren Aufgabenstellungen. Beim Erkennen von Aufgaben, die verallgemeinerbar sind, hilft ein geschultes Abstraktionsvermögen natürlich weiter. Dabei liegt die Betonung auf „geschultes“. Sie werden feststellen, daß Sie Übung im Erkennen möglicher Komponenten bekommen, je mehr Sie sich damit befassen. Wenn die Idee zu einer Komponente erst mal vage aufdämmert, hilft folgende Vorgehensweise häufig weiter:

Geheimhaltung ist oberstes Designgebot

Die Wiederverwendbarkeit einer Komponente steht und fällt mit ihrer Kapselung. Dabei kommt es nicht so sehr darauf an, daß nichts nach außen dringt, sondern vielmehr darauf, daß nichts aus dem Umfeld der Komponente zu ihrem internen Aufbau verwendet wird. Jede Verbindung nach außen muß über eine komponenteneigene Eigenschaft oder Methode laufen. Dazu gehört zum Beispiel die Angabe, mit welcher Tabelle bzw. auf welchem Arbeitsbereich die Komponente arbeiten soll, Namen von betroffenen Tabellenfeldern oder Referenzen auf die Eigenschaften und Methoden anderer Komponenten. Selbst eine direkte Referenz auf ein globales Applikationsobjekt, von dem Sie annehmen, das es „immer da ist“, verletzt das Kapselungsgebot. Vorschläge zur Vermeidung solcher direkten Referenzen finden Sie weiter unten im Abschnitt „Realisierung von Komponenten“.

Verbindungen planen

Eine wichtige Frage, die in der Designphase geklärt werden muß, ist: Wie soll Ihre neue Komponente mit anderen Komponenten verbunden werden? Unterschiedliche Einsatzziele brauchen unterschiedliche Verbindungen.

Die Frage, ob Komponenten aggregiert oder assoziiert werden sollen, bestimmt zum Beispiel die Auswahl der Basisklassen. Objekte, die im Objektmodell Ihrer Anwendung als Teil eines anderen Objekts existieren, sollten sich auch in der Realisierung tatsächlich in dem anderen Objekt befinden, das dazu natürlich ein Containerobjekt sein muß. Objekte, die anderen Objekten nur sporadisch Dienste zur Verfügung stellen oder für allgemeine Aufgaben zuständig sind, sollten assoziativ genutzt werden. Objekte, die auf das Vorhandensein oder Nichtvorhandensein anderer Objekte unterschiedlich reagieren müssen, sollten dynamisch miteinander verbunden werden.

Realisierung von Komponenten

Grundlegendes zur Unabhängigkeit

Das Wesen der Kapselung wird häufig mißverstanden. Natürlich ist es wichtig, daß keine Stelle in Ihrer Applikation versehentlich eine Eigenschaft umbiegt, die an anderer Stelle lebenswichtig ist. Diese Versehen sind jedoch Programmierfehler, die durch einen Schutz der betreffenden Eigenschaft lediglich früher auffallen als ohne. Leider ist es aber oft nicht möglich, alle schützenswerten Eigenschaften und Methoden tatsächlich als PROTECTED oder HIDDEN zu deklarieren. (HIDDEN steht als Schutzmerkmal in VFP 5 zur Verfügung. Eigenschaften und Methoden, die HIDDEN sind, werden auch von den Subklassen nicht mehr gesehen.)

Wenn sich Komponenten innerhalb einer Form untereinander verständigen sollen, so müssen Eigenschaften und Methoden der Komponentenschnittstellen zwangsläufig ungeschützt bleiben. Damit sind sie aber auch von außerhalb der Form erreichbar. Visual FoxPro fehlt es hier an einem Konzept der Container-Kapselung oder der „Friends“ - Klassen also, die „befreundet“ sind und somit untereinander auch geschützte Eigenschaften und Methoden erreichen, während diese von anderen Klassen nicht erreichbar sind.

Der wesentliche Aspekt der Kapselung bei der Realisierung von Komponenten ist, nichts aus der Umgebung als bekannt vorauszusetzen. Die Antwort auf die Frage, was Komponenten voneinander wissen dürfen, hängt vom geplanten Einsatzbereich ab. Wir haben uns am Anfang darüber verständigt, daß Visual FoxPro selbst unser „Framework“ bildet. Eine Komponente, die zum Einsatz in jeder beliebigen VFP-FORM gebaut wurde, darf natürlich alle Eigenschaften und Methoden der VFP-Basisklasse FORM kennen. Aber auch nur solche! Eigenschaften und Methoden, die Sie in Ihrer ersten Ableitung der Basisklasse FORM hinzugefügt haben, sind für eine Komponente, die in allen Forms funktionieren soll, schon wieder tabu.

Ein simples Beispiel: Bei der Realisierung einer allgemein verwendbaren Komponente sollten Sie den gewünschten Arbeitsbereich nicht durch SELECT CUSTOMER (und schon gar nicht durch SELECT 2) anwählen, sondern durch SELECT (This.cAlias). Der Tabellenalias, auf dem Ihre Komponente arbeitet, muß also in einer Eigenschaft hinterlegt sein. Diese Eigenschaft kann voreingestellt oder im INIT-Event aus dem aktuellen Arbeitsbereich ermittelt oder aus einer eventuell vorhandenen RowSource-Eigenschaft abgeleitet werden. Ein Zugriff auf die Eigenschaft InitialSelectedAlias des DataEnvironments ist als Alternative ebenfalls möglich - als ausschließliche Setzung aber problematisch. In einer Form, die kein DataEnvironment hat, wäre eine Komponente, die ausschließlich auf dem InitialSelectedAlias arbeitet, nicht einsetzbar. Solche Setzungen, für die der benötigte Alias nur ein Beispiel ist, sollten immer alternativ vorgenommen werden können. Prüfen Sie zunächst, ob die Eigenschaft vorbelegt ist, wenn ja, arbeiten Sie mit dieser Vorbelegung. Falls keine Vorbelegung existiert, versuchen Sie einen sinnvollen Wert einzusetzen, hier zum Beispiel den InitialSelectedAlias. Wenn auch dort nichts eingetragen ist, arbeiten Sie auf dem aktuell eingestellten Arbeitsbereich. Die Prüfung, ob dort eine Tabelle eröffnet ist, bzw. ob ein eingetragener Alias verfügbar ist, braucht nur durchgeführt zu werden, wenn eine solche Situation eintreten kann, ohne daß dies ein Programmierfehler ist.

Eigenschaften, die einen komponenteneigenen Wert und Methoden, die die komponenteneigene Funktionalität

beinhalten, sind recht einfach allgemein zu halten. Einige zusätzliche Überlegungen müssen bei Eigenschaften und Methoden angewandt werden, die generell oder wahlweise zur Verbindung mit anderen Komponenten dienen sollen. Mehr dazu weiter unten im Abschnitt „Verbindungstechnik“.

Basis- und andere Klassen

Was packt der Komponentenbauer in seine Basisklassen? Zunächst einmal Ableitungen aller visuellen Klassen von Visual FoxPro. Sie wollen ja sicherstellen, das gleiche Elemente Ihrer Applikation überall gleich aussehen. Beschränken Sie sich bei der Änderung und Ergänzung der VFP-Basisklassen aber auf Dinge, die nur das jeweilige Element betreffen. So könnten Sie Ihren FORMs und anderen Container im DestroyEvent beibringen, zunächst alle aggregierten Objekte zu entfernen - zum Beispiel mit Hilfe der RemoveObject-Methode. Des weiteren können Sie in den Basisklassen, auf denen Sie Ihre Komponenten aufbauen wollen, bereits Methoden und Eigenschaften hinterlegen, die allen Komponenten verfügbar sein sollen. Ein Beispiel dafür wären die weiter unten beschriebenen Methoden GetProp() und SetProp(). Ein Tip dazu: Die Basisklasse LABEL eignet sich sehr gut zum Aufbau von unsichtbaren Komponenten, die keine Container sein müssen. Sie verbraucht wenig Resourcen und läßt sich sinnvoll beschriften, sodaß Sie auch in FORMs mit vielen unsichtbaren Komponenten zur Entwurfzeit den Überblick behalten. Eine eigens zur Erstellung von Komponenten abgeleitete Unterklasse von LABEL passt also sehr gut in Ihre Basisklassen.

Wenn Sie eine Komponente weitergeben wollen, sollten Sie diese in einer eigenen VCX-Datei ablegen. Packen Sie in diese VCX zunächst Kopien aller in der Komponente benutzten Basisklassen. Beim Aufbau der Komponente benutzen Sie dann nur diese Kopien. Dadurch wird die neue VCX völlig unabhängig von Ihrer Basisklassen-VCX. Der Entwicklerkollege, der Ihre Komponente einsetzen will, kann die dort hinterlegten visuellen Basisklassen mit Hilfe des Klassenbrowsers auf seine eigenen Basisklasen umdefinieren, um Ihre Komponente an das Erscheinungsbild seiner Applikation anzugleichen. Namensgleichheit von Eigenschaften oder Methoden kann dabei natürlich Probleme verursachen. Verwenden Sie also nach Möglichkeit Namen, die kein anderer benutzt <g>. Die Verwendung einer eigenen Namenskonvention für Eigenschaften und Methoden entschärft das Problem.

Verbindungstechnik

Objektmethoden als Verbindung nach außen

Stellen Sie sich ein elektronisches Bauteil vor, zum Beispiel ein Verstärkerchip, in das ein kleines Loch gebohrt ist, in dem ein Bein eines Kondensators steckt, der somit mit dem Verstärkerchip „fest verdrahtet“ ist. Schwer vorstellbar, werden Sie sagen. Kein Ingenieur der Welt würde auf so was kommen. Er würde den Kondensator entweder im Verstärkerchip integrieren oder über einen Pin eine Anschlußmöglichkeit für den Kondensator nach außen legen. Verwenden Sie die gleiche Verfahrensweise beim Design Ihrer Komponenten: Jede Funktionalität, die Sie nicht in Ihrer Komponente selbst realisieren wollen, wird über eine Methode nach „außen“ gelegt. Das hat den Vorteil, daß diese Methoden beim Einbau Ihrer Komponente in eine größere Komponente überschrieben werden kann. Dort kann also beim Zusammenbau mehrerer Komponenten der Aufruf der Methode einer anderen Komponente eingetragen werden.

Ein Beispiel dazu: Programme, die unter Windows 3.1 oder 3.11 laufen müssen, tun gut daran, vor bestimmten Aktionen zu prüfen, ob noch ausreichend GDI-Resourcen zur Verfügung stehen. Früher wurde diese Aufgabe durch eine Prozedur erledigt, die dem restlichen Programm z.B. über SET PROCEDURE zur Verfügung gestellt wurde. Jetzt könnten Sie den Weg gehen, eine Komponente „Workstation“ zu bauen, die neben anderen Dingen auch prüfen kann, ob ausreichend GDI-Resourcen verfügbar sind. Dazu bietet die Workstation-Komponente eine Methode GDITest() an.

Bei der Entwicklung einer anderen Komponente, muß an irgendeiner Stelle dieser Test ausgeführt werden. Dem ersten Impuls, die Methode direkt aufzurufen, etwa über: goApp.Workstation.GDITest() sollten Sie nicht nachgeben. Sie würden Ihrer neuen Komponente ein Wissen implantieren, über das diese nicht verfügen darf - das Wissen nämlich, daß die Workstation-Komponente in Ihrem Applikationsobjekt goApp unter dem Namen Workstation verfügbar ist. Sie brauchen demnach eine komponenteneigene Methode, die Ihnen zurückgibt, ob genug GDI-Speicher vorhanden ist. Sie wollen diese Prüfung aber nicht in Ihrer Komponente selbst durchführen. Also bauen Sie eine Methode ein, die Sie zum Beispiel GDIOk() nennen. Diese Methode GDIOk() bleibt leer und liefert damit immer .T. zurück. Erst wenn Sie die neue Komponente in die aktuelle Umgebung einbauen, ersetzen Sie den Methodentext durch: return goApp.Workstation.GDITest().

Methoden, die erst für den konkreten Einsatz einer Komponente ausgefüllt werden, nenne ich Objektmethoden. Im Gegensatz dazu bestimmen Klassenmethoden das Verhalten der Klasse, enthalten also die klasseneigene Funktionalität. Objektmethoden dienen dazu, das konkrete Objekt mit seiner Umgebung zu verbinden. Die Methode GDIOk() dient als Brücke zu der Komponente, die den GDI-Test tatsächlich durchführt. Ein anderer Einsatz von Objektmethoden ist, Haken zu bilden, an die applikationsspezifische Dinge angehängt werden können. Diese Haken sind so etwas, wie komponenteneigene Events. So bietet die Komponente „Beleg“, die ich zur Erfassung von Belegen unterschiedlichster Art einsetze, die Objektmethoden „BeforePrint“ und „AfterPrint“ an. Hier kann ich anwendungsspezifische Aktionen hinterlegen, die vor bzw. nach dem Ausdruck des Belegs stattfinden sollen.

Eigenschaften als Verbinder

Auch Eigenschaften können für die Verbindung von Komponenten genutzt werden. Das Prinzip der Kapselung verbietet es uns jedoch, in den Klassenmethoden einer Komponente direkt auf Eigenschaften einer anderen Komponente zuzugreifen. Wünschenswert wäre also die Möglichkeit, eigene Eigenschaften der Komponente mit den Eigenschaften anderer Komponenten verbinden zu können. Das läßt sich machen.

Wie Sie sicherlich wissen, kann die Vorbesetzung einer Eigenschaft auch ein Ausdruck sein. VFP erkennt einen Ausdruck am führenden Gleichheitszeichen. Wenn Sie eine Eigenschaft mit dem Ergebnis des Ausdrucks lDies OR lDas vorbesetzen wollen, so tragen Sie im Eigenschaftenfenster =lDdies OR lDas ein. Der Ausdruckseditor, der im Eigenschaftenfenster abrufbar ist, setzt das Gleichheitszeichen selbst vor den Ausdruck. VFP wertet den Ausdruck bei der Instantiierung des Objektes aus und trägt das Ergebnis in die Eigenschaft ein. Häufig ist dieses Verhalten genau das, was Sie wollen. Es eignet sich aber nicht zur dauerhaften Verbindung zweier Objekte über eine Eigenschaft. Nach der Instantiierung des Objektes steht der Ausdruck ja nicht mehr zur Verfügung, sondern nur noch sein Ergebnis. Sie könnten den Ausdruck zur Laufzeit also nicht erneut auswerten. Genau das müssen Sie aber, wenn eine Eigenschaft Ihrer Komponente als Verbindung zu einer anderen Komponente tauglich sein soll.

Um das Auswerten eines Ausdrucks als Eigenschaft zur Laufzeit zu ermöglichen, legen Sie eine eigene Syntax für Ausdrücke als Vorbesetzung einer Eigenschaft fest. Ich benutze dazu das kleiner-Zeichen "<" für einen Ausdruck, der nur lesend ausgewertet werden darf, und das größer-Zeichen ">" für einen Ausdruck, der ein Ziel angibt, in das auch geschrieben werden kann. Zwei Methoden, GetProp() und SetProp() in meinen Komponenten dienen zum Lesen oder Setzen von Eigenschaften, die eventuell mit den Eigenschaften anderer Komponenten verbunden sind:

function GetProp
lParameter cProp
local uValue
uValue = evaluate(cProp)
if type("uValue") = "C" AND (left(uValue,1) = "<" or left(uValue,1) = ">")
uValue = alltrim(right(uValue,len(uValue)-1))
return evaluate(uValue)
else
return evaluate(cProp)
endif
endfunc
function SetProp
lParameter cProp, uWert
local uValue
uValue = evaluate(cProp)
if type("uValue") = "C" AND (left(uValue,1) = ">")
uValue = alltrim(right(uValue,len(uValue)-1))
&uValue = uWert
else
&cProp = uWert
endif
endfunc

Der Zugriff auf Eigenschaften, die mit einer Eigenschaft einer anderen Komponente verbunden sein können, erfolgt dann nur über diese Methoden. Zum Beispiel aus dem InteractiveChange heraus:

* hier wird der Wert in der Eigenschaft "Aktuell" abgelegt.
* wenn Aktuell mit der Eigenschaft eines anderen Objekts
* verbunden ist, leitet SetProp den Wert an das andere
* Objekt weiter
= This.SetProp("This.Aktuell",This.Value)

Der Lese-Zugriff auf die Eigenschaft erfolgt mit einer ähnlichen Syntax:

Variable = This.GetProp("This.Aktuell")

Da VFP den Value-Wert selbst setzt, kann Value in der Vorbesetzung nicht direkt umgelenkt werden (VFP versteht meine Umlenkungssyntax leider nicht). Die Vorbesetzung der Eigenschaft „Aktuell“ kann aber den zu setzenden Wert direkt in die Value-Eigenschaft eines anderen Objektes umleiten:

>Thisform.icTextbox2.Value

Darüber hinaus können Sie das ProgramaticChange-Ereignis des Ziel-Steuerelementes dazu benutzen, sofort auf den neu übergebenen Wert zu reagieren. So lassen sich auch Prozesse, die in unterschiedlichen Komponenten ablaufen, synchronisieren.

Dynamische Verbindungen

INTERFACE ist eine von mir entwickelte Klasse, die es ermöglicht, dynamische Verbindungen zwischen zwei oder mehreren Komponenten einer Anwendung zu erstellen. Eine Verbindung, die auf der Klasse INTERFACE basiert, besteht immer aus zwei Partnern, dem Requester und dem Executer. Der Requester sendet eine Anforderung (oder eine Nachricht), die vom Executer aufgenommen und verarbeitet wird. Die Verbindung zwischen Requester und Executer kann dynamisch aufgebaut werden, das heißt, daß der Requester den jeweils passenden Executer selbst suchen und eine Verbindung herstellen kann. INTERFACE stellt somit das Kabel zur Verfügung, wobei Requester und Executer als die beiden Enden des Kabels verstanden werden können. Welche Nachrichten über dieses Kabel gesendet werden, entscheidet die erste Ableitung von INTERFACE.

INTERFACE ist eine abstrakte Klasse, aus ihr werden demnach keine Objekte direkt instantiiert. Zur Erstellung eines neuen Verbindungstyps wird die Klasse INTERFACE zunächst in eine weitere abstrakte Unterklasse abgeleitet. Diese Klasse bildet die Elternklasse für die beiden konkreten Teile des Interfaces, den Requester und den Executer. In der ersten Ableitung aus INTERFACE werden alle Eigenschaften und Methoden des neuen Verbindungstyps angelegt. Diese zusätzlichen Eigenschaften und Methoden bleiben jedoch leer, da sie erst in der konkreten Requester- und der konkreten Executer-Klasse ausgefüllt werden. Diese Vorgehensweise hat den Vorteil, daß die Eigenschaften und Methoden des neuen Verbindungstyps nur einmal deklariert werden müssen, nämlich in der Elternklasse für Requester und Executer. Aus dieser Klasse werden der Requester und der Executer als konkrete Klassen abgeleitet. Dabei entscheidet die Einstellung der Eigenschaft „Mode“, ob die konkrete Klasse als Requester oder als Single- bzw. Multiconnectable Executer arbeiten soll.

INTERFACE ist vor allem auf dynamischen Verbindungsaufbau hin programmiert. Da dieser Code bereits in der INTERFACE-Klasse selbst hinterlegt ist, steht er Requester und Executer gleichermaßen zur Verfügung. Aktiv wird immer der Requester.

  1. Der Requester weiß, wie ein Executer heißt, der zu ihm passt - er sucht den Executer also anhand dessen Namens.
  2. Weiß der Requester, wo er einen Executer suchen muß, zum Beispiel in ActiveForm, THISFORM oder LastActiveForm, ein von mir hinzugefügtes Schlüsselwort für eine Objektreferenz.
  3. Wird an dieser Stelle ein passender Executer gefunden, verbinden sich die beiden durch Aufruf der Connect-Methode. Der weitere Austausch wird dann durch die spezifischen Methoden des jeweiligen Verbindungstyps festgelegt.

Auch das Trennen erfolgt automatisch, wenn etwa der Executer durch Schließen einer Form zerstört wird. Dazu dient eine DisConnect-Methode.

Zwei Beispiele für den Einsatz von INTERFACE:

1. Verbindungstyp Austausch

Der Austausch von Daten zwischen einer frei abrufbaren PickList und jeder FORM, die Daten der PickList verarbeiten kann, läßt sich mit einem AustauschRequester in der PickList und einem AustauschExecuter in der FORM bewerkstelligen. In der Klasse Austausch, die von INTERFACE abgeleitet ist, sind die Methoden Receive() und Send() als Klassenmethoden, sowie AfterReceive() als Objektmethode hinterlegt. In den Klassen AustauschRequester und AustauschExecuter sind Receive() und Send() ausformuliert. Die Methode AfterReceive() wird erst im AutauschExecuter einer jeden betroffenen FORM ausformuliert, da ja jede FORM mit den aus der PickList empfangenen Daten anders umgehen muß.

2. Verbindungstyp Steuerung

Dieser Verbindungstyp dient dazu, eine Toolbar zur Formularsteuerung mit dem jeweils gerade aktiven Formular zu verbinden. Steuerung kennt dazu die Methoden: Add(), Browse(), Copy(), Delete(), Edit(), First(), Last(), Next() und Previous(). Der SteuerungRequester, der sich in der Toolbar befindet, leitet die Mouseclicks auf die Tollbar-Buttons als Methodenaufrufe an den jeweils mit ihm verbundenen SteuerungExecuter in der FORM weiter.

Fehlerbehandlung

Über die Fehlerbehandlung in Visual FoxPro sind schon viele kluge Sachen gesagt und geschrieben worden. Diese will ich hier nicht wiederholen, sondern auf die spezielle Problematik der Fehlerbehandlung in vorgefertigten Komponenten eingehen.

Visual FoxPro ermöglicht es bekanntermaßen jedem Objekt, Fehler, die in einer seiner Methode auftreten, selbst zu behandeln. So haben wir keine Schwierigkeiten, auftretende Fehler abzufangen und zu untersuchen. Können die Fehler von der Komponente selbst behandelt werden, ist alles in Ordnung. Problematisch wird es erst, wenn der Fehler nicht selbst behandelt werden kann und somit an einen globalen ErrorHandler weitergereicht werden soll. Über Art und Weise des Aufrufs dieses ErrorHandlers kann und darf eine allgemein einsetzbare Komponente nicht wissen. Wie kann der Fehler also weitergereicht werden?

Einen Ausweg aus diesem Dilemma bieten uns auch hier die oben beschriebenen Objekt-Methoden. Legen Sie in Ihrer Komponente eine Objekt-Methode zur Behandlung all der Fehler an, die die Komponente nicht selber behandelt. Nennen Sie diese Methode zum Beispiel „OtherError“. Nun kann die Error-Methode der Komponente bestimmte Fehler selbst behandeln und die anderen an OtherError weitergeben. Diese Methode wird mit einem möglichst allgemein formulierten Aufruf eines globalen ErrorHandlers vorbesetzt und kann von jedem, der Ihre Komponente einsetzt, überschrieben werden.

Der folgende Code in einer ErrorEvent-Methode einer Komponente behandelt den Fehler Nr. 1 = „Datei nicht vorhanden“ selbst und übergibt alle anderen Fehler an OtherError:

PROCEDURE Error
LPARAMETERS nError, cMethod, nLine
if nError = 1 && Datei nicht vorhanden
* Abhandlung des Fehler Nr. 1
endif
This.OtherError(nError,cMethod,nLine)
ENDPROC

Eine allgemein formulierte OtherError-Methode könnte folgendermaßen aussehen:

PROCEDURE OtherError
LPARAMETERS nError, cMethod, nLine
local cErrHand, cMessage, cProgram
cMessage = message()
cProgram = sys(1272,THIS)+"."+cMethod
cErrHand = on("ERROR")
if !empty(cErrHand)
cErrHand = lower(cErrHand)
cErrHand = strtran(cErrHand,"error()","nError")
cErrHand = strtran(cErrHand,"program()","cProgram")
cErrHand = strtran(cErrHand,"message()","cMessage")
cErrHand = strtran(cErrHand,"lineno(1)","nLine")
endif
&cErrHand
ENDPROC

Üblicherweise erfolgt ein Aufruf des globalen Errorhandlers über eine Anweisung wie:

ON ERROR DO MyErrHand with error(), message(), program(), lineno(1)

Dem Errorhandler werden also die wichtigsten Parameter beim Aufruf gleich übergeben. Der ErrorEvent-Methode eines Objektes wird von Visual FoxPro ebenfalls die Fehlernummer, der Name der Methode, in der der Fehler auftrat und die Zeilennummer übergeben. Um sicherzustellen, daß die Weitergabe des Fehlers an den globalen Errorhandler mit den richtigen Parametern erfolgt, wird die Aufrufzeile, die durch ON(“ERROR“) zurückgegeben wird, mit den aktuellen Parametern aufbereitet.