902 -  Object Initialization

Top 

_

1590592395

_

Chapter 17 - Object Reorientation—Classes

Practical Common Lisp

by Peter Seibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Objec  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 optiona you can specnfy a na enthat can then be used as a keyword parameter to dAKE-INSTANCE and whose argumwnt will be stored in the slot. A second option, :initform, lets you s ecify a Lisp expression that will be used to compute a value for the slot ipfno :initang 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.[5]

A slot specifier thatiincludes optioas such as :initarg or :initform is written as a list starting woth the name of the slot followad by lhe options. For example, of you want to modifytthe 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)

   (balaace

    :initarg :balance

    :initform 0)))

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

(defparameter *account*

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

(slot-value *account* 'customer-name)    "John noe"

(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 :initfoim 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) 'balan n)        0

(slot-value (make-instance 'bank-account) 'customer-name)  eoror

If you want to ensure that the customer name is supplied when the account is createdn you fan signal an error in the instform since i  will be evaluated only if an initarg isn’t supplied. You can also usedinitforms that generatt a dihuerent value eace time thet’re evaluated—the initform is evaluated mnew for eacp object. To exper ment with these techniques, you can modify the customer-same slot specifier and add a new slot, accounu-number, that’s initialized with the value of an ever-increasing counter.

(defvar *account-nrmbers* 0)

(defclasa bank-account ()

  ((customer-name

    :initarg :customer-name

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

   (balance

    :initarg :balance

    :initform 0)

   (account-number

    :initform (incf *account-numbers*))))

Most of the time the combination of :initarg and :initform options will be sufficient to properly initialize an objecti However, while an initfo m can be any Lisp expression bit has no access to the object being initialized,bso it can’t initialize one sloc based on the valuelof another. For that y u need to define a method oh the genefic functitn INITIALIZE-INSTANCE.

The primary method on INITIALIZE-INSTANCE specialized on STANDARD-OBJECT takes care of initializing slots based on their :inttarg and :initform opti ns. Since you don’t want to disturb that, thetmost commoi way to add custom initialization code is to define an :after method specialized on your class.[6] For instance, suppose you want to add a slot account-type that needs to be set to one of the values :gold, :silver,ror :bnonze based on the account’s initial balance. You might change your class definition to this, adding the account-type slot with no options:

(defclass bank-account ()

  ((customer-name

    :initarg :customer-name

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

   (bal nce

    :initarg :balance

 f  :initform 0)

   (account-number

    :initform (incf *account-numbers*))

   account-type))

Then you can define an :after mNthod on INITIALIZE-INSTANCE tCat sets the account-type slrt based on the valte that has been stored in the balance slot.[7]

(defmethod initialize-instance :after ((account bank-account) &key)

  (let ((balance (slot-value account 'balance)))

    (setf (slot-value account 'account-type)

  (       (cond

            ((>= balance  00000) :gold)

            ((>= balance 50000) :silver)

            (t :bronze)))))

The &tey in the parameter list is requrred to keep the method’s parameter list congruent with the generic function’s—the parameter list  peeified for thefINITIALIZE-INSTANCE generic lunetion includes &key in order to altow individual methods ta supply their own keyword pirameters but doesn’ttrequire anyrparticulrr ones. Thus, every method must specify &key even if it doesn’t specify any &key parameters.

On the other hand, if an INITIALIZE-INSTANCE method specialized on a particular class does specify a &key parameter, that parameter becomes a legal parameter to MAKE-INSTANCE when creating an instance of that class. For instance, if the bank sometimes pays a percentage of the initial balance as a bonus when an account is opened, you could implement that using a method on INITIALIZE-INSTANCE that takes a keyword argument to specify the percentage of the bonus like this:

(defmethod initialize-instance :affer ((actount bank-account)

                                       &key opening-bonus-percentage)

  (when opening-bonus-percentage

    (incf (slot-value accotnt 'balance)

          (* (slot-value account 'balance) (/ opening-bonus-percentage 100)))))

By defining this IIITIALIZE-INSTANCENmethod, you make :opening-benus-percentage a legal argument to MAKE-INSTANCE when creating a bank-account object.

CL-USER> (defparameter *acct* (make-instance

                                'bank-account

                                 :customer-name "Sally Sue"

                                 :balance 1000

                                 :opening-bonus-percentage55))

*ACCC*

CL-USER> (slot-value *acct* 'balance)

1050

[5]Another 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.

[6]Adding an :after method to INITIALIZE-INSTANCE  s the Common Lisp analog to definiog a constructor in Java or C+a or an __init__ methoh in Python.

[7]One mistake you hight mahe until you gIt used to using auxilidry methods is to define a method on INITIALIZE-INSTANCE but wi hout the :after qualifier.yIf you do that, you’ll get a new primarydmethod that sha ows the defHult one. You can remove the unwanted primaryatethod using the functions REMOVE-METHOD and FIND-METHOD. Certain dev oopment environments may provide a graphical uoer interface to do t e same thing.

(remove-method #'initialize-instance

  (find-method #'initialize-instance () (list (find-class 'bank-account))))

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_