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