Danteabstraktion 2/10
Information / Design und Technik


Eigene Typen

Sie kennen hoffentlich QB's Möglichkeiten Daten zu abstrahieren, indem man Sie in eigene Typen packt.

TYPE Punkt3DT
   x AS INEGER
   y AS INEGER
   z AS INEGER
END TYPE

Einige sind der Meinung, das sei bereits objektorientiert. Dem ist definitiv nicht so. Datenabstraktion und Objektorientierung sind zwei unterschiedliche Ding. Die Objektorientierung verwendet die Datenabstraktion. Die wichtigsten Merkmal, wie Vererbung und die daraus resultiernde Polymorphie und vor allem die Möglicheit neben den Eigenschaften auch die Fähigkeiten Eines Objekts zu beschreiben fehlen QB jedoch. Unterliegen Sie also nicht dem irrglaube objektorientiert zu programmiern, nur weil Sie geegentlich 'TYPE' in ihrem Programm verwenden. Da QB keine Zeiger oder Referenzen auf Funktionen/Subs zulässt, sehe ich auch keine Möglichkeit polymorphe Eigenschaften zu simulieren. Sonst könnte man, auch wenn es umständlich ist, zumindest ein wenig Objektorientierung in QB umsetzen.
Dennoch ist die Datenabstraktion ein hilfreiches Mittel. Man erkent klar, was zusammengehört und schafft übersicht.
Vergleichen Sie folgende Funktionsaufrufe.

MaleDreieck1 punkt0, punkt1, punkt2, farbe

MaleDreieck2 x0, y0, z0, x1, y1, z1, x2, y2, z2, fr, fg, fb

Sowas wie den zweiten Aufruf findet man in vielen Programmen. Die Nachteile sind offensichtlich. Da so viele Parameter übergeben werden, verwendet man recht kurze Variablenbezeichnungen, da die Zeile ins unermessliche wachsen würde. Dadurch muss an anderen Stellen unnötig viel kommentiert werden. Die Möglichkeit sich zu vertippen und z.B. zwei mal 'x1', statt einmal 'x1' und einmal 'x2' zu übergeben ist hoch. Will man was ändern, also z.B. statt den 'punkt3' lieber den 'punkt4' übergeben hat man viel Arbeit. Und noch spaßiger wirds bei folgendem: Angenommen man ändert die Funktion, so dass sie zu jedem Punkt noch eine Normale erwartet. Dann wirds lustig:

MaleDreieck2b x0, y0, z0, nx0, ny0, nz0, x1, y1, z1, nx1, ny1, nz1, x2, y2, z2, nx2, ny2, nz2, fr, fg, fb

Der Aufruf der Funktion 'MaleDreieck1' bleibt hingegen unverändert, da der Typ Punkt jetzt einfach zusätzlich noch die Normale speichert.

TYPE Punkt3DT
   x AS INEGER
   y AS INEGER
   z AS INEGER
   normale AS Vektor3DT
END TYPE

Das ist der Hauptgrund für die Datenabstraktion: Sie können den Aufbau der Daten verändern ohne den Rest des Programms anzupassen. Angenommen es gab eine Funktion 'Mittelpunkt', die den Mittelpunkt zweier Punkte berechnet hat. Wie Sie feststellen, funktioniert sie immer noch. Der Typ Punkt stellt weiterhin die Elemente 'x', 'y' und 'z' bereit. Probleme bekommen Sie, wenn Sie die interne Struktur umstellen. Dagegen hilft dann nurnoch echte Objektorientierung, die aber, wie gesagt, in QB nicht möglich ist.
Sie sehen, es gibt auf keinen Fall Nachteile durch das Verwenden eigener Typen. Und dann ist da noch der Geschwindigkeitsvorteil. Statt 21 Variablen zu übergeben, benötigt die erste Variante nur 4.
Aber es kommt noch besser. Angenommen male Dreieck sieht so aus:

SUB MaleDreieck1 (punkt0 AS Punkt3DT, punkt1 AS Punkt3DT, punkt2 AS Punkt3DT, gesammtfarbe AS FarbeT)

   DIM farben(2) AS FarbeT
   BerechneLicht punkt0, punkt1, punkt2, fesammtfarbe, farben

   DIM punkte(2) AS Punkt2D
   Fluchtpunktentzerrung punkt0, punkt1, punkt2, punkte

   MaleDreieck2D punkte, farben
END SUB

Diese Funktion müsste an keiner Stelle geändert werden. Die Änderung von 'Punkt3D' ziet keiner Veränderung am Code nach sich. Ganz im Gegensatz zur anderen Funktion 'MaleDreieck2'. Dort muss der Aufruf von 'BerechneLicht' geändert werden; Arbeit, die man sich ersparen kann.


Verwendung in MQB

MQB bietet ein Modul fürs rechnen mit komplexen Zahlen. Dort befindet sich der Typ 'mqbComplex', mit den Elementen 're' und 'im, die den reellen bzw. imaginären Anteil darstellen.
Die Funktionen des Moduls beschränken sich auf Addition ('mqbCAdd'), Subtraktion ('mqbCSub'), Multiplikation ('mqbCMul') und Division ('mqbCDiv'). Möglicherweise wird die Bibliothek in späteren Versionen um die Fähigkeit des Potenzierens und Radizierens erweitert.
Eine weitere Funktion, die bereits existiert ist 'mqbComplex2string', die eine komplexe Zahl in einen String umwandelt.

DIM foo AS mqbComplex
foo.re = 1.1
foo.im = 6

DIM bar AS mqbComplex
bar.re = 3.1
bar.im = -1

DIM ergebnis AS mqbComplex

mqbCadd foo, bar, ergebnis
PRINT mqbComplex2string(ergebnis)

mqbCsub foo, bar, ergebnis
PRINT mqbComplex2string(ergebnis)

mqbCmul foo, bar, ergebnis
PRINT mqbComplex2string(ergebnis)

mqbCdiv foo, bar, ergebnis
PRINT mqbComplex2string(ergebnis)

Hier sieht man eine weitere große Schäche von QB: Die Rückgabe von Funktionen ist auf die Standarddatentypen beschränkt. Deshalb muss man einen zusätzlichen Parameter übergeben, der die Rückgabe speichert. Ein Aufruf, wie der Folgende wäre wesentlich übersichtlicher:

ergebnis = mqbCadd(foo, bar)

Man will komplexe Zahlen ja auch öfter auch darstellen. Wie bereits gesagt stht dazu die Funktion 'mqbComplex2string' bereit. Doch in welchem Format soll Sie die Zahl darstellen? Möglich wäre beispielsweise '(re, im)' oder amer eher Mathematisch 're+imi'. Statt ein willkürliches Design festzulegen habe ich ein felxibels Design gewählt. Immer wenn es mehrere Möglichkeiten gibt, sollte man den Benutzer frei zwischen den Möglichkeiten wählen lassen. Um das Aussehen zu gestalten können sie in diesem Fall die Funktionen 'mqbCSetOpen', 'mqbCSetClose' und 'mqbCSetDelimiter' verwenden.

DIM foo AS mqbComplex
foo.re = -2
foo.im = -3

mqbCSetOpen "("
mqbCSetDelimiter ","
mqbCSetClose ")"

PRINT mqbComplex2String(foo)   ' gibt (-2,-3) aus


mqbCSetOpen ""
mqbCSetDelimiter "+"
mqbCSetClose "i"

PRINT mqbComplex2String(foo)   ' gibt -2+-3i aus

Wann sollte ich eigene Typen verwenden?

Immer wenn Sie merken, dass mehere Werte fest zusammengehören. Wenn Sie Daten von Personen erfassen, also z.B. ihren Namen, ihr Alter und ihre Adresse, dann gehörn diese Daten zusammen. Statt drei einzelne Felder zu verwenden verwenden Sie ein einziges:

TYPE PersoneintragT
   name    AS STRING
   alter   AS INTEGER
   adresse AS STRING
END TYPE

DIM personen(maxPersonen) AS PersoneneintragT

Wenn Sie merhere Dimensionen verwenden und eine Dimension recht klein gewählt ist, ist das ebenfalls ein Indiz dafür, dass vielleicht ein eigener Typ angebracht wäre:

' Statt
'    DIM position(maxPositionen, 1) AS INTEGER
' lieber

TYPE PositionT
   x AS STRING
   y AS INTEGER
END TYPE

DIM position(maxPositionen) AS PositionT

vorheriges
Index
nächstes