(defclass savings-account (bank-account) ...)

I'll discuss in the section 'Multiple Inheritance' what it means to list more than one direct superclass in direct-superclass-names.

Slot Specifiers

The bulk of a DEFCLASS form consists of the list of slot specifiers. Each slot specifier defines a slot that will be part of each instance of the class. Each slot in an instance is a place that can hold a value, which can be accessed using the SLOT-VALUE function. SLOT-VALUE takes an object and the name of a slot as arguments and returns the value of the named slot in the given object. It can be used with SETF to set the value of a slot in an object.

A class also inherits slot specifiers from its superclasses, so the set of slots actually present in any object is the union of all the slots specified in a class's DEFCLASS form and those specified in all its superclasses.

At the minimum, a slot specifier names the slot, in which case the slot specifier can be just a name. For instance, you could define a bank-account class with two slots, customer-name and balance, like this:

(defclass bank-account ()

(customer-name

balance))

Each instance of this class will contain two slots, one to hold the name of the customer the account belongs to and another to hold the current balance. With this definition, you can create new bank-account objects using MAKE-INSTANCE.

(make-instance 'bank-account) ==> #<BANK-ACCOUNT @ #x724b93ba>

The argument to MAKE-INSTANCE is the name of the class to instantiate, and the value returned is the new object.[184] The printed representation of an object is determined by the generic function PRINT-OBJECT. In this case, the applicable method will be one provided by the implementation, specialized on STANDARD- OBJECT. Since not every object can be printed so that it can be read back, the STANDARD-OBJECT print method uses the #<> syntax, which will cause the reader to signal an error if it tries to read it. The rest of the representation is implementation-defined but will typically be something like the output just shown, including the name of the class and some distinguishing value such as the address of the object in memory. In Chapter 23 you'll see an example of how to define a method on PRINT-OBJECT to make objects of a certain class be printed in a more informative form.

Using the definition of bank-account just given, new objects will be created with their slots unbound. Any attempt to get the value of an unbound slot signals an error, so you must set a slot before you can read it.

(defparameter *account* (make-instance 'bank-account)) ==> *ACCOUNT*

(setf (slot-value *account* 'customer-name) 'John Doe') ==> 'John Doe'

(setf (slot-value *account* 'balance) 1000) ==> 1000

Now you can access the value of the slots.

(slot-value *account* 'customer-name) ==> 'John Doe'

(slot-value *account* 'balance) ==> 1000

Object Initialization

Since you can't do much with an object with unbound slots, it'd be nice to be able to create objects with their slots already initialized. Common Lisp provides three ways to control the initial value of slots. The first two involve adding options to the slot specifier in the DEFCLASS form: with the :initarg option, you can specify a name that can then be used as a keyword parameter to MAKE-INSTANCE and whose argument will be stored in the slot. A second option, :initform, lets you specify a Lisp expression that will be used to compute a value for the slot if no :initarg argument is passed to MAKE-INSTANCE. Finally, for complete control over the initialization, you can define a method on the generic function INITIALIZE-INSTANCE, which is called by MAKE- INSTANCE.[185]

A slot specifier that includes options such as :initarg or :initform is written as a list starting with the name of the slot followed by the options. For example, if you want to modify the definition of bank-account to allow callers of MAKE-INSTANCE to pass the customer name and the initial balance and to provide a default value of zero dollars for the balance, you'd write this:

(defclass bank-account ()

((customer-name

:initarg :customer-name)

(balance

:initarg :balance

:initform 0)))

Now you can create an account and specify the slot values at the same time.

(defparameter *account*

(make-instance 'bank-account :customer-name 'John Doe' :balance 1000))

(slot-value *account* 'customer-name) ==> 'John Doe'

(slot-value *account* 'balance) ==> 1000

If you don't supply a :balance argument to MAKE- INSTANCE, the SLOT-VALUE of balance will be computed by evaluating the form specified with the :initform option. But if you don't supply a :customer-name argument, the customer-name slot will be unbound, and an attempt to read it before you set it will signal an error.

(slot-value (make-instance 'bank-account) 'balance) ==> 0

(slot-value (make-instance 'bank-account) 'customer-name) ==> error

If you want to ensure that the customer name is supplied when the account is created, you can signal an error in the initform since it will be evaluated only if an initarg isn't supplied. You can also use initforms that generate a different value each time they're evaluated—the initform is evaluated anew for each object. To experiment with these techniques, you can modify the customer-name slot specifier and add a new slot, account-number, that's initialized with the value of an ever-increasing counter.

(defvar *account-numbers* 0)

(defclass bank-account ()

((customer-name

:initarg :customer-name

:initform (error 'Must supply a customer name.'))

(balance

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату