Benutzer:West of House/Common Lisp Object System

Das Metaobject-Protocol bei CLOS

Bearbeiten

Dem Metaobject-Protocol liegt die Idee zugrunde, das Objektsystem mit den Mitteln des Objektsystems selbst darzustellen. Es ordnet somit die Begriffe Klasse, Methode, Generische Funktion usw selbst in einem Klassensystem an. Definiert der Benutzer eine neue Klasse, so wird eine Instanz der Klasse standard-class gebildet, die mit den Nutzerangeben zur Klasse (Slots, Superklassen) etc gefüllt wird. Dieses Objekt heisst Metaobjekt der neuen Klasse und es kann über alle Eigenschaften der Klasse Auskunft geben. Wird mit diesem Objekt die Generische Funktion make-instance aufgerufen, so entsteht eine Instanz der neuen Klasse.

Abb x zeigt das Common Lisp Object System als Vererbungsdiagramm. Abstrakt gesehen handelt es sich um eine vollkommen traditionelle Hierarchie aus Klassen. Technisch betrachtet ist es ein Netzwerk aus Metaobjekten, die (mit wenigen Ausnahmen) Instanzen der Klasse standard-class sind.

Da die Klasse standard-class durch dieses Klassensystem aber überhaupt erst definiert wird, enpuppt sich die Kontruktion als ein Henne-Ei-Problem, ähnlich wie bei Compilern, der in der Sprache geschrieben ist, aus der sie selbst übersetzen. Die Lösung erfolgt dieses Problems erfolgt auch genau wie bei Compilern durch Bootstrapping.

Dem Nutzer des Metaobject Protocol eröffnet diese Besonderheit eine Reihe von Möglichkeiten, die andere Objektsysteme nicht bieten können: Insbesondere kann er die Klassen der Metaobjekte nach eigenen Bedürfnissen anpassen und damit die Eigenschaften Objektsystems selbst verändern. Innerhalb dieses "offenen" Objektsystems können somit beliebige Cross Cutting Concerns bedient werden. Es ist also genau das erreichbar, aktuell mit Aspektorientierter Programmierung versucht wird.

 

Das Klassendiagramm zeigt die an CLOS beteiligten 26 Klassen. Sie werden dargestelt durch 26 Metaobjekte. Die 22 schwarz umrandeten Metaobjekte sind Instanzen von "standard-class" (einschließ STANDARD-CLASS selbst). Abstrakte Klassen sind als Ellipsen gezeichnet, nicht abstrakte Klassen als Rechtecke.

Im Folgenden ist jeweils mit my-class die Klasse als Konzept und mit MY-CLASS das diese Klasse implementierende Metaobjekt gemeint. Die eingefärbten Klassen-Metaobjekte FUNCALLABLE-STANDARD-CLASS, STANDARD-CLASS und BUILT-IN-CLASS implementieren die Klassen, denen die Metaobjekte angehören. Dabei gehören T und FUNCTION der Klasse built-in-class an und GENERIC-FUNCTION und STANDARD-GENERIC-FUNCTION der Klasse funcallable-standard-class. Alle anderen Metaobjekte gehören zur Klasse standard-class.

Die doppelt gerahmten klassen standard-generic-function, standard-method und standard-class sind die standard Metaobjekt-Klassen für die Anwendungsentwicklung. Um Instanzen von diesen zu bilden, stellt CLOS die Makros defgeneric, defmethod, und defclass zur Verfügung. Für die OOP im konventionellen Sinne reichen diese Makros aus.

Beispiel einer Klassendefinition

Bearbeiten

defclass erzeugt im einfachsten Fall eine eine Klasse, unter Verwendung des Metaobjekts STANDARD-CLASS:

(defclass point (standard-object) 
  ((x :initarg :x) 
   (y :initarg :y)))

Die Definition führt dazu, daß ein Metaobjekt POINT der Metaobjekt-Klasse standard-class erzeugt wird. In das Metaobjekt werden die Angaben zu den beiden Slot-Definition und die (hier einzige) Elternklasse standard-object eingetragen.

Ab sofort ist dieses Metaobjekt namens POINT in der Lage, Instanzen der von ihm implementierten Klasse point zu erzeugen, indem die Generische Funktion make-instance mit diesem aufgerufen wird. Die beiden in der Klassendefinition mit :initarg vereinbarten Schlüsselwörter :x und :y werden dabei verwendet, um initiale Werte zuzweisen.

(make-instance 'point :x 10 :y 12)

Es kann eine Methode definiert werden, die auf die Klasse point spezifiziert ist z.B. die Entfernung des Punktes zum Ursprung berechnet:

(defmethod origin-distance ((p point))
  (sqrt (+ (expt (slot-value p 'x) 2) 
           (expt (slot-value p 'y) 2))))

Da es zu diesem Zeitpunkt noch keine Generische Funktion für diese Methode gibt, wird diese automatisch miterzeugt. Dabei erfolgt unter Umständen eine Warnung des Lisp-Systems.

Die Generische Funktion origin-distanceund damit die neu geschaffene Methode kann wie folgt mit dem Punkt (3,4) aufgerufen werden:

(origin-distance (make-instance 'point :x 3 :y 4))

Lisp antwortet korrekt mit 5.0 .

Erzeugung eigener Klassen-Metaobjekte

Bearbeiten

Das Beschriebene ist der normale Weg, Klassen zu erzeugen. Es besteht aber nicht nur die Möglichkeit, neue Klassen zu bilden, sondern auch eine neue "Art von Klassen" anzulegen. Diese werden manchmal Metaklasse genannt. Die Bezeichnung ist deswegen etwas unglücklich, weil sie ein neues Konzept suggeriert, obwohl es sich dabei nur um die Anlage eines neuen Metaobjektes handelt. Besser ist daher die Bezeichnung Klassen-Metaobjekt (Der Name :metaclass kommt aber auch in CLOS selbst vor, wie weiter unten zu sehen ist).

Angenommen, der Anwender möchte in einem Softwareprojekt erreichen, dass seine Klassen immer die durch sie insgesamt erzeugten Instanzen mitzählen. Dazu muss eine von der Klasse standard-class abgeleitete, spezialisierte Klasse counting-class, also ein neues Metaobject COUNTING-CLASS, erzeugt werden, das dann an Stelle von STANDARD-CLASS verwendet wird:

(defclass counting-class (standard-class)
   ((counter :initform 0)))

[Steel Bank Common Lisp 1]

Damit der Zähler bei jeder Instanzbildung inkrementiert wird, wird der vordefinierten Generischen Funktion make-instance eine neue Methode zugefügt:

(defmethod make-instance :after ((class counting-class) &key)
   (incf (slot-value class 'counter)))

:after bewirkt, das die neue, auf counting-class stärker spezialisierte Methode die weniger spezielle auf standard-class spezialisierte Methode nicht ersetzt, sondern zu dieser derart hinzukombiniert wird, dass diese nach jener ausgeführt wird. Dabei handelt es sich um eine von mehreren vordefinierten Methodenkombination (Im Klassendiagramm von CLOS findet sich hierzu die Klasse method-combination, über die dieses Verhalten implementiert wird und abgewandelt werden kann).

Nun kann das neue Klassen-Metaobjekt COUNTING-CLASS benutzt werden, um z.B. eine Klasse counted-points zu definieren. Dazu erfolgt bei defclass der Zusatz :metaclass counting-class. Der Einfachheit halber wird von der oben definierten Klasse point abgeleitet, sodaß keine zusätzlichen Slot-Definitionen in der Klasse mehr erforderlich sind.

(defclass counted-point (point) 
  () 
  (:metaclass counting-class))

Wie gehabt, können nun Instanzen mit make-instance erzeugt werden:

(make-instance 'counted-point :x 2 :y 4)
(make-instance 'counted-point :x 2 :y 3)
(make-instance 'counted-point :x 2 :y 1)

Bei jeder Instantiierung hat sich nun der Slot counter im Metaobjekt COUNTED-POINT um 1 erhöht. Er kann folgendermaßen abgefragt werden:

(slot-value (find-class 'counted-point) 'counter)

Es wird dann der Wert 3 ausgegeben.

Was hier gezeigt wurde kann weniger formal als die Definition einer neuen "Klassen-Klasse" verstanden werden und ist die Abwandlung des Klassensystems von Common Lisp mit Mitteln genau dieses Klassensystems selbst. Derartige Abänderungen der Eigenschaften sind sinngemäß genauso an den Konzepten Methode, Methoden-Kombination, Slot-Definition, Generische Funktion und so weiter möglich.

Steel Bank Common Lisp

Bearbeiten
  1. SBCL macht an dieser Stelle noch die ergänzende Definition (defmethod sb-mop:validate-superclass ((class counting-class) (super standard-class)) t) erforderlich. Sonst kommt es bei der späteren Instatiierung zu einem Fehler.