| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
4.4.1 Class hierarchy and inheritance of slots 4.4.2 Instance creation and slot access 4.4.3 Slot description 4.4.4 Class precedence list
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(define-class A () a) (define-class B () b) (define-class C () c) (define-class D (A B) d a) (define-class E (A C) e c) (define-class F (D E) f) |
A, B, C have a null list of super classes. In this
case, the system will replace it by the list which only contains
<object>, the root of all the classes defined by
define-class. D, E, F use multiple
inheritance: each class inherits from two previously defined classes.
Those class definitions define a hierarchy which is shown in Figure 1.
In this figure, the class <top> is also shown; this class is the
super class of all Scheme objects. In particular, <top> is the
super class of all standard Scheme types.
|
The set of slots of a given class is calculated by taking the union of the
slots of all its super class. For instance, each instance of the class
D, defined before will have three slots (a, b and
d). The slots of a class can be obtained by the class-slots
primitive. For instance,
(class-slots A) => ((a)) (class-slots E) => ((a) (e) (c)) (class-slots F) => ((e) (c) (b) (d) (a) (f)) |
Note: The order of slots is not significant.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Creation of an instance of a previously defined
class can be done with the make procedure. This
procedure takes one mandatory parameter which is the class of the
instance which must be created and a list of optional
arguments. Optional arguments are generally used to initialize some
slots of the newly created instance. For instance, the following form
(define c (make <complex>)) |
will create a new <complex> object and will bind it to the c
Scheme variable.
Accessing the slots of the new complex number can be done with the
slot-ref and the slot-set! primitives. Slot-set!
primitive permits to set the value of an object slot and slot-ref
permits to get its value.
(slot-set! c 'r 10) (slot-set! c 'i 3) (slot-ref c 'r) => 10 (slot-ref c 'i) => 3 |
Using the describe function is a simple way to see all the
slots of an object at one time: this function prints all the slots of an
object on the standard output.
First load the module (oop goops describe):
|
The expression
(describe c) |
will now print the following information on the standard output:
#<<complex> 401d8638> is an instance of class <complex>
Slots are:
r = 10
i = 3
|
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
When specifying a slot, a set of options can be given to the system. Each option is specified with a keyword. The list of authorized keywords is given below:
#:init-value permits to supply a default value for the slot. This
default value is obtained by evaluating the form given after the
#:init-form in the global environment, at class definition time.
#:init-thunk permits to supply a thunk that will provide a
default value for the slot. The value is obtained by evaluating the
thunk a instance creation time.
#:init-keyword permits to specify the keyword for initializing a
slot. The init-keyword may be provided during instance creation (i.e. in
the make optional parameter list). Specifying such a keyword
during instance initialization will supersede the default slot
initialization possibly given with #:init-form.
#:getter permits to supply the name for the
slot getter. The name binding is done in the
environment of the define-class macro.
#:setter permits to supply the name for the
slot setter. The name binding is done in the
environment of the define-class macro.
#:accessor permits to supply the name for the
slot accessor. The name binding is done in the global
environment. An accessor permits to get and
set the value of a slot. Setting the value of a slot is done with the extended
version of set!.
#:allocation permits to specify how storage for
the slot is allocated. Three kinds of allocation are provided.
They are described below:
#:instance indicates that each instance gets its own storage for
the slot. This is the default.
#:class indicates that there is one storage location used by all
the direct and indirect instances of the class. This permits to define a
kind of global variable which can be accessed only by (in)direct
instances of the class which defines this slot.
#:each-subclass indicates that there is one storage location used
by all the direct instances of the class. In other words, if two classes
are not siblings in the class hierarchy, they will not see the same
value.
#:virtual indicates that no storage will be allocated for this
slot. It is up to the user to define a getter and a setter function for
this slot. Those functions must be defined with the #:slot-ref
and #:slot-set! options. See the example below.
To illustrate slot description, we shall redefine the <complex> class
seen before. A definition could be:
(define-class <complex> (<number>) (r #:init-value 0 #:getter get-r #:setter set-r! #:init-keyword #:r) (i #:init-value 0 #:getter get-i #:setter set-i! #:init-keyword #:i)) |
With this definition, the r and i slot are set to 0 by
default. Value of a slot can also be specified by calling make
with the #:r and #:i keywords. Furthermore, the generic
functions get-r and set-r! (resp. get-i and
set-i!) are automatically defined by the system to read and write
the r (resp. i) slot.
(define c1 (make <complex> #:r 1 #:i 2)) (get-r c1) => 1 (set-r! c1 12) (get-r c1) => 12 (define c2 (make <complex> #:r 2)) (get-r c2) => 2 (get-i c2) => 0 |
Accessors provide an uniform access for reading and writing an object
slot. Writing a slot is done with an extended form of set!
which is close to the Common Lisp setf macro. So, another
definition of the previous <complex> class, using the
#:accessor option, could be:
(define-class <complex> (<number>) (r #:init-value 0 #:accessor real-part #:init-keyword #:r) (i #:init-value 0 #:accessor imag-part #:init-keyword #:i)) |
Using this class definition, reading the real part of the c
complex can be done with:
(real-part c) |
new-value variable
can be done using the extended form of set!.
(set! (real-part c) new-value) |
Suppose now that we have to manipulate complex numbers with rectangular
coordinates as well as with polar coordinates. One solution could be to
have a definition of complex numbers which uses one particular
representation and some conversion functions to pass from one
representation to the other. A better solution uses virtual slots. A
complete definition of the <complex> class using virtual slots is
given in Figure 2.
<complex> number class definition using virtual slots
|
This class definition implements two real slots (r and
i). Values of the m and a virtual slots are
calculated from real slot values. Reading a virtual slot leads to the
application of the function defined in the #:slot-ref
option. Writing such a slot leads to the application of the function
defined in the #:slot-set! option. For instance, the following
expression
(slot-set! c 'a 3) |
permits to set the angle of the c complex number. This expression
conducts, in fact, to the evaluation of the following expression
((lambda o m)
(let ((m (slot-ref o 'm)))
(slot-set! o 'r (* m (cos a)))
(slot-set! o 'i (* m (sin a))))
c 3)
|
A more complete example is given below:
|
Since initialization keywords have been defined for the four slots, we
can now define the make-rectangular and make-polar standard
Scheme primitives.
(define make-rectangular (lambda (x y) (make <complex> #:r x #:i y))) (define make-polar (lambda (x y) (make <complex> #:magn x #:angle y))) |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A class may have more than one superclass. (4) With single inheritance (one superclass), it is easy to order the super classes from most to least specific. This is the rule:
Rule 1: Each class is more specific than its superclasses. |
With multiple inheritance, ordering is harder. Suppose we have
(define-class X () (x #:init-value 1)) (define-class Y () (x #:init-value 2)) (define-class Z (X Y) (...)) |
In this case, the Z class is more specific than the X or
Y class for instances of Z. However, the #:init-value
specified in X and Y leads to a problem: which one
overrides the other? The rule in GOOPS, as in CLOS, is that the
superclasses listed earlier are more specific than those listed later.
So:
Rule 2: For a given class, superclasses listed earlier are more
specific than those listed later.
|
These rules are used to compute a linear order for a class and all its
superclasses, from most specific to least specific. This order is
called the "class precedence list" of the class. Given these two
rules, we can claim that the initial form for the x slot of
previous example is 1 since the class X is placed before Y
in class precedence list of Z.
These two rules are not always enough to determine a unique order,
however, but they give an idea of how things work. Taking the F
class shown in Figure 1, the class precedence list is
(f d e a c b <object> <top>) |
However, it is usually considered a bad idea for programmers to rely on exactly what the order is. If the order for some superclasses is important, it can be expressed directly in the class definition.
The precedence list of a class can be obtained by the function
class-precedence-list. This function returns a ordered
list whose first element is the most specific class. For instance,
(class-precedence-list B) => (#<<class> B 401b97c8>
#<<class> <object> 401e4a10>
#<<class> <top> 4026a9d8>)
|
However, this result is not too much readable; using the function
class-name yields a clearer result:
(map class-name (class-precedence-list B)) => (B <object> <top>) |
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |