Good Object-Oriented Design
That’s about it for the main features of Common Lisp’s object system. If you have lots of experience with object-oriented programming, you can probably see how Common Lisp’s features can be used to implement good object-oriented designs. However, if you have less experience with object orientation, you may need to spend some time absorbing the object-oriented way of thinking. Unfortunately, that’s a fairly large topic and beyond the scope of this book. Or, as the man page for Perl’s object system puts it, “Now you need just to go off and buy a book about object-oriented design methodology and bang your forehead with it for the next six months or so.” Or you can wait for some of the practical chapters, later in this book, where you’ll see several examples of how these features are used in practice. For now, however, you’re ready to take a break from all this theory of object orientation and turn to the rather different topic of how to make good use of Common Lisp’s powerful, but sometimes cryptic, **FORMAT**
function.
1Defining new methods for an existing class may seem strange to folks used to statically typed languages such as C++ and Java in which all the methods of a class must be defined as part of the class definition. But programmers with experience in dynamically typed object-oriented languages such as Smalltalk and Objective C will find nothing strange about adding new behaviors to existing classes.
2In other object-oriented languages, slots might be called fields, member variables, or attributes.
3As when naming functions and variables, it’s not quite true that you can use any symbol as a class name—you can’t use names defined by the language standard. You’ll see in Chapter 21 how to avoid such name conflicts.
4The argument to **MAKE-INSTANCE**
can actually be either the name of the class or a class object returned by the function **CLASS-OF**
or **FIND-CLASS**
.
5Another way to affect the values of slots is with the :default-initargs
option to **DEFCLASS**
. This option is used to specify forms that will be evaluated to provide arguments for specific initialization parameters that aren’t given a value in a particular call to **MAKE-INSTANCE**
. You don’t need to worry about :default-initargs
for now.
6Adding an :after
method to **INITIALIZE-INSTANCE**
is the Common Lisp analog to defining a constructor in Java or C++ or an __init__
method in Python.
7One mistake you might make until you get used to using auxiliary methods is to define a method on **INITIALIZE-INSTANCE**
but without the :after
qualifier. If you do that, you’ll get a new primary method that shadows the default one. You can remove the unwanted primary method using the functions **REMOVE-METHOD**
and **FIND-METHOD**
. Certain development environments may provide a graphical user interface to do the same thing.
(remove-method #'initialize-instance
(find-method #'initialize-instance () (list (find-class 'bank-account))))
8Of course, providing an accessor function doesn’t really limit anything since other code can still use **SLOT-VALUE**
to get at slots directly. Common Lisp doesn’t provide strict encapsulation of slots the way some languages such as C++ and Java do; however, if the author of a class provides accessor functions and you ignore them, using **SLOT-VALUE**
instead, you had better know what you’re doing. It’s also possible to use the package system, which I’ll discuss in Chapter 21, to make it even more obvious that certain slots aren’t to be accessed directly, by not exporting the names of the slots.
9One consequence of defining a **SETF**
function—say, (setf foo)
--is that if you also define the corresponding accessor function, foo
in this case, you can use all the modify macros built upon **SETF**
, such as **INCF**
, **DECF**
, **PUSH**
, and **POP**
, on the new kind of place.
10The “variable” names provided by **WITH-SLOTS**
and **WITH-ACCESSORS**
aren’t true variables; they’re implemented using a special kind of macro, called a symbol macro, that allows a simple name to expand into arbitrary code. Symbol macros were introduced into the language to support **WITH-SLOTS**
and **WITH-ACCESSORS**
, but you can also use them for your own purposes. I’ll discuss them in a bit more detail in Chapter 20.
11The Meta Object Protocol (MOP), which isn’t part of the language standard but is supported by most Common Lisp implementations, provides a function, class-prototype
, that returns an instance of a class that can be used to access class slots. If you’re using an implementation that supports the MOP and happen to be translating some code from another language that makes heavy use of static or class fields, this may give you a way to ease the translation. But it’s not all that idiomatic.
12In other words, Common Lisp doesn’t suffer from the diamond inheritance problem the way, say, C++ does. In C++, when one class subclasses two classes that both inherit a member variable from a common superclass, the bottom class inherits the member variable twice, leading to no end of confusion.