January 5, 2008
Methods of customizing CLOS objects
CLOS objects provided by other people, for example as parts of libraries, are always a generalization of the concept they represent. What are the ways in which we can model our own objects, with their peculiar presets?
Suppose this simplified class from a layman’s biological classification library:
(in-package :bio) (defclass flower (plant) ((stem-color :initform nil) (petal-color :initform nil)))
Suppose further that accessors and initargs are named like the slots themselves.
Now let’s try to model a white rose; green stem, white petals. What choices do we have?
Class specialization
The next best thing:
(defclass white-rose (flower) ((bio::stem-color :initform 'green) (bio::petal-color :initform 'white))) (make-instance 'white-rose)
Watch out for package scoping when overriding slots!
This is a little bit ugly because we have to override the protection of the “bio“ package.
Also, any additional slot arguments (accessors, initargs) defined in some parent class are probably lost.
But instantiation is really clean, simple and straight-forward. As an added bonus you can customize the class beyond presets for existing slots: new slots are easily added.
See the comments section for a better version of this approach.
Constructor function
This method uses a simple function setting up the general object in a customized way and returning it:
(defun make-white-rose () (make-instance 'flower :stem-color 'green :petal-color 'white))
Nice and without package lock intrusion, but not as obvious as class specialization.
initialize-instance :after-method
This is a variation on the first method. It might seem a bit intimidating, but works suprisingly well.
(defclass white-rose (flower) ()) (defmethod initialize-instance :after ((self white-rose)) (setf (stem-color self) 'green) (setf (petal-color self) 'white))
initialize-instance gets called after make-instance to initialize the slots of the new object. We provide an :after method so the built-in method can do its housekeeping beforehand (assign specified initargs to slots, for example).
How do you provide specialized presets?
Comments(5)
I think you’re looking for :DEFAULT-INITARGS:
(defclass flower (plant)
((stem-color :initform nil :initarg :stem-color)
(petal-color :initform nil :initarg :petal-color)))
(defclass white-rose (flower)
()
(:default-initargs :stem-color ‘green :petal-color ‘white))
Thanks for the contribution, Luís.
It obsoletes the package lock instrusion of way #1.
One thing that bit me about :default-initargs is that it does not seem to be inherited by subclasses.
It does for me:
CL-USER(4): (defclass foo () ((s :initarg :s))(:default-initargs :s 5))
#<STANDARD-CLASS FOO>
CL-USER(5): (defclass bar (foo) nil)
#<STANDARD-CLASS BAR>
CL-USER(6): (slot-value (make-instance ‘bar) ‘s)
5
I’m using it all the time this way. Maybe there’s some other problem?
Yes, looks like I was doing something else wrong.