Previous | Table of Contents | Next |
The method combination described in the previous section is known as standard method combination. CLOS supports combining methods in other ways. In particular, CLOS supports operator method combination, which can be most easily understood as a standard Lisp operator applied to all the applicable primary methods:
(defgeneric weight (x) (:method-combination +))
The weight method uses the + method combination and any defmethod on weight must have + as the second argument. Heres some code that defines classes with weights:
(defclass chassis () ()) (defclass engine () ()) (defclass wheels () ()) (defclass car (chassis engine wheels) ()) (defmethod weight + ((ch chassis)) 1500) (defmethod weight + ((e engine)) 500) (defmethod weight + ((w wheels)) 150)
Calling weight on an instance of car returns the sum of the individual weights:
(weight (make-instance car)) returns 2150
The operators supported are +, and, append, list, max, min, nconc, or, and progn.
CLOS permits a great deal of flexibility in class redefinition. Evolving applications requires changing the design, sometimes radically. Getting the design right is usually an incremental process that can take many steps, and the ability to do this interactively greatly speeds the development process.
Many CAD applications permit classes to be redefined at runtime and the instances must be updated accordingly.
It is sometimes convenient to change the class of instances. For example, in a scheduling application, tasks may belong to classes that represent real resources, and in trying different schedules, you might not need to consider a particular task. The easiest way to handle this may be to change the class of the task to one where all the methods returning the resource constraints instead return no resource use.
5.4.8.1. Class Redefinition
Classes can be redefined at any time. The new class definition completely replaces the old one. The class redefinition may include adding or deleting slots, changing its superclasses, changing accessors, or changing any other option to defclass.
The new class redefinition affects not only all instances of the class, but all its subclasses and their instances, and CLOS automatically propagates the changes to all the affected elements. As with the rest of CLOS, the change can be customized (as discussed in the next section).
CLOS specifies that updating an instance happens before a slot of the instance is accessed, and CLOS only updates instances as needed. This lazy update model ensures that all instances appear correctly updated as soon as the class is changed while actually expending the cost of the update over only the instances that are used.
The slots of an instance are updated according to the following default rules:
As an example, I redefine square. The original definition is
(defclass square (convex-region) ((number-of-sides :initform 4 :allocation :class) (lower-left :initform (make-point :x 0 :y 0) :initarg :lower-left :accessor lower-left) (upper-right:initform (make-point :x 1 y 1) :initarg :upper-right :accessor upper-right)))
and making an instance is
(setf *s* (make-instance square :lower-left (make-instance point :x 2 :y 2) :upper-right (make-instance point :x 3 :y 3)))
Now I redefine it as
(defclass square (convex-region) ((number-of-sides :allocation :class) (xlow :initarg :xlow :accessor xlow :initform 0) (xhigh :initarg :xhigh :accessor xhigh :initform 1) (ylow :initarg :ylow :accessor ylow :initform 0) (yhigh :initarg :yhigh :accessor yhigh :initform 1)))
The only slot that is preserved is number-of-sides.
You need to update *s* properly, and you do it by putting an after method on update-instance-for-redefined-class. Note that the method definition has to be done before the new class definition; otherwise, if *s* were accessed after the class redefinition but before you defined the method, the slots would be defaulted to the new initforms:
(defmethod update-instance-for-redefined-class :after ((sq square) new-slots old-slots plist &rest initargs) (let ((ll (getf plist lower-left)) (ur (getf plist upper-right))) (when (and ll ur) (setf (slot-value sq xlow) (x-coordinate ll)) (setf (slot-value sq ylow) (y-coordinate ll)) (setf (slot-value sq xhigh) (x-coordinate ur)) (setf (slot-value sq yhigh) (y-coordinate ur)))))
On accessing the instance (xlow *s*), you get the value 2.
To preserve the original interface, you use the following:
(defmethod lower-left ((sq square)) (make-instance point :x (xlow sq) :y (ylow sq))) (defmethod upper-right ((sq square)) (make-instance point :x (xhigh sq) :y (yhigh sq)))
Previous | Table of Contents | Next |