(defmethod beat ((drum snare-drum) (stick wooden-drumstick)) ...)
(defmethod beat ((drum snare-drum) (stick brush)) ...)
(defmethod beat ((drum snare-drum) (stick soft-mallet)) ...)
(defmethod beat ((drum tom-tom) (stick wooden-drumstick)) ...)
(defmethod beat ((drum tom-tom) (stick brush)) ...)
(defmethod beat ((drum tom-tom) (stick soft-mallet)) ...)
Multimethods don't help with the combinatorial explosion—if you need to model five kinds of drums and six kinds of sticks, and every combination makes a different sound, there's no way around it; you need thirty different methods to implement all the combinations, with or without multimethods. What multimethods do save you from is having to write a bunch of dispatching code by letting you use the same built-in polymorphic dispatching that's so useful when dealing with methods specialized on a single parameter.[180]
Multimethods also save you from having to tightly couple one set of classes with the other. In the drum/stick example, nothing requires the implementation of the drum classes to know about the various classes of drumstick, and nothing requires the drumstick classes to know anything about the various classes of drum. The multimethods connect the otherwise independent classes to describe their joint behavior without requiring any cooperation from the classes themselves.
I've covered the basics—and a bit beyond—of generic functions, the verbs of Common Lisp's object system. In the next chapter I'll show you how to define your own classes.
17. Object Reorientation: Classes
If generic functions are the verbs of the object system, classes are the nouns. As I mentioned in the previous chapter, all values in a Common Lisp program are instances of some class. Furthermore, all classes are organized into a single hierarchy rooted at the class T
.
The class hierarchy consists of two major families of classes, built-in and user-defined classes. Classes that represent the data types you've been learning about up until now, classes such as INTEGER
, STRING
, and LIST
, are all built-in. They live in their own section of the class hierarchy, arranged into appropriate sub- and superclass relationships, and are manipulated by the functions I've been discussing for much of the book up until now. You can't subclass these classes, but, as you saw in the previous chapter, you can define methods that specialize on them, effectively extending the behavior of those classes.[181]
But when you want to create new nouns—for instance, the classes used in the previous chapter for representing bank accounts—you need to define your own classes. That's the subject of this chapter.
You create user-defined classes with the DEFCLASS
macro. Because behaviors are associated with a class by defining generic functions and methods specialized on the class, DEFCLASS
is responsible only for defining the class as a data type.
The three facets of the class as a data type are its name, its relation to other classes, and the names of the slots that make up instances of the class.[182] The basic form of a DEFCLASS
is quite simple.
(defclass
(
What Are 'User-Defined Classes'? |
The term |
As with functions and variables, you can use any symbol as the name of a new class.[183] Class names are in a separate namespace from both functions and variables, so you can have a class, function, and variable all with the same name. You'll use the class name as the argument to MAKE-INSTANCE
, the function that creates new instances of user-defined classes.
The STANDARD- OBJECT
. Any classes listed must be other user-defined classes, which ensures that each new class is ultimately descended from STANDARD-OBJECT
. STANDARD-OBJECT
in turn subclasses T
, so all user-defined classes are part of the single class hierarchy that also contains all the built-in classes.
Eliding the slot specifiers for a moment, the DEFCLASS
forms of some of the classes you used in the previous chapter might look like this:
(defclass bank-account () ...)
(defclass checking-account (bank-account) ...)