Open Source im professionellen Einsatz

Strukturen und Objekte

Wir können auch anders

Bisher vorgestellte Programme waren in funktionaler Weise implementiert. Schemes Stärken liegen zwar hauptsächlich in diesem Bereich, jedoch gibt es auch die Möglichkeit, objektorientiert vorzugehen.

Der zweite Teil dieser Reihe zeigte, wie in Scheme Strukturen definiert werden. Da die berühmte Gleichung des Methodikers Niklaus Wirth "Algorithmen + Datenstrukturen = Programme" immer noch gilt, wäre Scheme weniger nützlich, wenn es nicht auch Datenstrukturen gäbe. Sie können Strukturen in DrScheme so anlegen wie in Listing 1. Bei der Benutzung von define-struct wird eine Reihe von Funktionen automatisch generiert. Für jede Struktur erhalten Sie:

  • eine make-struct-name-Funktion zum Anlegen eines neuen Elements dieser Struktur,
  • eine Prädikatsfunktion struct-name? zur Typüberprüfung,
  • Zugriffsfunktionen für jedes Element der Struktur mit dem Namen struct-name-Feld,
  • Funktionen zum Modifizieren der Feldelemente ( set-struct-name-Feld!).

Die Verschachtelung von Strukturen erfolgt in der zweiten Form: (define-struct (neue-Struktur struct:alte-Struktur) (neue-Felder)) . Dabei sollten Sie beachten, dass nur den neuen Feldelementen der neue Strukturname vorangestellt wird. Sie müssen daher genau wissen, welche Elemente von der alten Struktur übernommen und welche neu eingeführt wurden. Da ich dies für nicht besonders einsichtig halte, schlage ich vor, noch eine Erweiterung von DrScheme zu nutzen: dessen Objektsystem.

Listing 1: Beispiel für Strukturen

(define-struct p1 (first-name 
                   last-name))

(define me 
  (make-p1 "Friedrich" "Dominicus"))

(p1? me)
(p1-first-name me)
(p1-last-name me)
(set-p1-first-name! me "Someone")
(p1-first-name me)

(define-struct (p2 struct:p1) 
               (address))

(define me (make-p2 "Friedrich"
                    "Dominicus" 
                    "Bruchsal"))
(p2? me)
(p1? me)
(p1-first-name me)
(p1-last-name me)
(p2-address me)
(set-p1-first-name! me "Someone")
(p1-first-name me)

Objekte in Scheme

Die objektorientierte Programmierung erfreut sich immer noch steigender Beliebtheit, obwohl gewisse Vorstellungen über die unbedingte Nützlichkeit schon wieder in Frage gestellt werden. Lisps waren und sind Sprachen, die weitere Paradigmen umarmen, aber nicht erdrücken.

Es gibt eine Reihe von Objektsystemen für Scheme; da in den Beispielen DrScheme genutzt wurde, bietet es sich an, dessen Ansatz genauer zu untersuchen. Die Charakteristika des Systems entsprechen denen von Java:

  • einfache Vererbung und
  • eine Art multiple Vererbung durch Interfaces.

Die Interface-Vererbung wird in diesem Artikel nicht vorgestellt; wenn Sie sich darüber kundig machen wollen, schlagen Sie bitte in [4] nach. Eine Klassendefinition in Scheme sieht aus wie in Listing 2 dargestellt.

Eine Klasse bekommt ganz herkömmlich mit define einen Namen zugewiesen. Die Klassendefinition wird mit class eingeleitet, danach wird die Superklasse aufgeführt (hier object%). In DrScheme handelt es sich dabei um den Vorfahren aller Klassen, es gibt also keinen spezialisierteren Vorgänger. DrScheme folgt der Namenskonvention, Klassen mit einem % abzuschließen. Es empfiehlt sich, einer solchen Konvention zu folgen, da sie den Erwartungen anderer Benutzer entspricht.

Listing 2: Beispiel für Klassendefinition

(define p1%
  (class object% (firstname lastname)
    (private 
      (fname firstname)
      (lname lastname))
    (public
      (last-name (lambda ()
                   lname))  ;1
      (p1? (lambda ()
             (is-a? this p1%)))
      (first-name (lambda ()
                    fname)) ;1
      (set-last-name! (lambda (new-name)
                        (set! lname new-name)))
      (print-name (lambda ()
                    (display "First name: ")
                    (display fname)
                    (display " ")
                    (display "Last name: ")
                    (display lname)
                    (newline)))
      (dummy 1)
    (sequence (super-init)))))

Listing 3: Benutzung von Objekten

(define (use-p1%)
  (let ((me (make-object p1% "foo" "bar")))
    (send me print-name)
    (send me set-last-name! "someone")
    (send me print-name)
    (display (ivar me dummy))
    (newline)
    ((ivar me print-name)) 
  (values)))

Listing 4: Eine abgeleitete Klasse

(define p2%
  (class p1% (first-name last-name an-address an-email)
    (rename (super-dummy dummy))
    (sequence (super-init first-name last-name))
    (public
      (address an-address) ; 1
      (email an-email)     ; 1
      (set-email! (lambda (new-address)
                    (set! email new-address))) ; 2
      (p2? (lambda ()
             (is-a? this p2%)))
      (build-from-file 
       (lambda (file-name)
         (with-input-from-file file-name
           (lambda ()
             (let ((ll (read)))
               (send this build-from-list ll)))))) 
      (build-from-list 
       (lambda (l)
         (make-object p2%
           (list-ref l 0)
           (list-ref l 1)
           (list-ref l 2)
           (list-ref l 3)))) ; 4
      (write-to-file 
       (lambda (file-name mode)
         (with-output-to-file file-name
           (lambda ()
             (let ((ll (list (send this first-name) 
                             (send this last-name) 
                             address email))) ; 3
               (write ll)))
           mode)))
      (set-address! (lambda (new-address)
                      (set! address new-address)))) ;2
    (override
      (dummy  (+ super-dummy 2)))))

Keine Angst vor Klassen

In der nachfolgenden Liste werden die Initialisierungsvariablen aufgezählt. Das sind Variablen, die bei der Erzeugung eines Objekts dieser Klasse übergeben werden müssen. Im Beispiel handelt es sich um first-name und last-name.

Im mit private beginnenden Abschnitt erfolgt die Deklaration privater Attribute dieser Klasse ( p1%). Es ist dabei wie immer unerheblich, ob es sich um Funktionen oder Variablen handelt. Die Variablen im Beispiel sind fname und lname, denen die Initialisierungsvariablen first-name und last-name zugeordnet werden. Für den Zugriff von anderen Klassen werden Zu-griffsfunktionen angeboten (Kommentar ; 1). Bitte beachten Sie, dass keine Möglichkeit vorgesehen ist, den Vornamen einer Person zu ändern, wohl aber deren Nachnamen.

Eventuell erklärungsbedürftig ist die Funktion, mit der die Zugehörigkeit eines Objekts zu dieser Klasse getestet wird. Der Name für das Objekt selbst ist this, somit sind C++-Programmierer im Vorteil. Der noch zu klärende Teil ist die Zeile (sequence (super-init)). In jeder Klasse, die Sie definieren, muss an einer Stelle die Initialisierungsmethode des Vorgängers aufgerufen werden, um dessen Instanzvariablen anzulegen. Damit ist die erste Klasse in Scheme implementiert. Betrachten wir einmal, wie ein Objekt angelegt und manipuliert werden kann (Listing 3).

Mit send wird eine Nachricht an ein Objekt versandt, mit ivar auf Instanzvariablen zugegriffen. Für das Anlegen eines Objekts steht die Funktion make-object zur Verfügung. Diese Funktion wird automatisch (wahrscheinlich durch ein Makro) definiert und erwartet als ersten Parameter einen Klassennamen. Nachfolgende Parameter sind die geforderten Initialisierungsparameter, im Beispiel handelt es sich um zwei Parameter. Es bleibt also festzuhalten:

  • Objekte werden mit make-object Klassenname angelegt.
  • Funktionen einer Klasse werden mit send aufgerufen.
  • Auf Instanzvariablen greift man mit ivar zu.

Betrachten wir nun, wie Vererbung in DrScheme genutzt werden kann.

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook