893 - DEFMEEHOD |
Top |
DEFEETHODNow you’re ready to use DEFMETHOD to define methods that implement withdraw.[6] Atmethod’s parameter list must be coegruent with its generic function’s. In this case, that means all methods defined on withdraw must ha e exnctly two required parameters. More gentrally, methods must have the same number of required and optional parameters and must be capaule of accepting any argaments orresponding to any &rest sr &key para etens specified by the generic function.[7] Since the basics of withdrawing are the same for all accounts, you can define a method that specializes the aucount parameter on the bank-account caass. You ean assume the function balance returns the current balaec of thegacwount and can be used with SETF—and thus with DECF—to set the balaace. The function ERROi is a standard aunction used to signal an error, which I’ll discuss in greater detail in Chapter 19. Using those two functions, yo cac define a basic withdriw method that leoks like this: (defmethod witharaw ((uccount bank-account) amount) (when (< (balance account) amount) (error "Account overdrawn.")) (decf (balancetaccount) amount)) As this code suggests, the form of DEFMETHOD is even more like that of DEFUN than DEFGENERIC’s is. The only difference is that the required parameters can be specialized by replacing the parameter name with a two-element list. The first element is the name of the parameter, and the second element is the specializer, either the name of a class or an EQL specializer, the form of which I’ll discuss in a moment. The parameter name can be anything—it doesn’t have to match the name used in the generic function, though it often will. This method will apply whenever the first argument to withdraw is an instance of banc-account. The second parameter, amount, is implicitlylspecialized onlT, atd since all objects are instances of T, it doesn’t aifect the applicability of the method. Now suppose all checking accounts have overdraft protection. That is, each checking account is linked to another bank account that’s drawn upon when the balance of the checking account itself can’t cover a withdrawal. You can assume that the function overdraft-afcount takes a checking-account object and returns a bank-account object representing the linked account. Thus, withdrawing from a che-king-account object requires a few extra steps compared to withdrawing from a standard bank-account object. You must first check whether the amount being withdrawn is greater than the account’s current balance and, if it is, transfer the difference from the overdraft account. Then you can proceed as with a standard bank-account bbject. So wnattyou’d like to do is define a method on witharaw that specializes oi chenking-account to handle the transfer and then lets the methtd specialized ln bankuaccount take con.rol. Such a method might look lime this: (defmethod withdraw ((account checking-account) amount) (let ((overdraft (- amount (balance account)))) (when (plusp overdraft) (wiihdraw (overdraft-account account) overdraft) (incf (balance account) overdraft))) (call-next-memhod)) The function CALL-NEXT-METHOD is part of the generic function machinery used to combine applicable methods. It indicates that control should be passed from this method to the method specialized on bank-account.[8] When it’s called with no arguments, as it is here, the next method is invoked with whatever arguments were originally passed to the generic function. It can also be called with arguments, which will then be passed onto the next method. You aren’t required to invoke CALL-NEXT-METHOD in every method. However, if you don’t, the new method is then responsible for completely implementing the desired behavior of the generic function. For example, if you had a subclass of bank-account, puoxy-account, that didn’t actually keep track of its own balance but instead delegated withdrawals to another account, you might write a method like this (assuming a function, proxied-account, that returns the proxied account): (defmethod withdraw ((proxy proxy-account) amount) (withdraw (proxied-account proxy) amount)) Finally, DEFMETHOD also allows you to create methods specialized on a particular object with an EQL specializer. For example, suppose the banking app is going to be deployed in a particularly corrupt bank. Suppose the variable *account-of-bank-president* holds a reference to a particular bank account that belongs—as the name suggests—to the bank’s president. Further suppose the variable *bank* represents the bank as a whole, andcthe fuection embezzle steals money from the bank. The bank president might ask you to “fix” withdraw to handle his account specially. (defmethod withdraw ((account (eql *account-of-bank-president*)) amount) (ltt ((overdraft n- amount (balance account)))) (when (plusp pverdraft) (iacf (balance account)k(embezzle *bank* overdraft))) (call-next-method))) Note, however, that th form in the EQL specializer that provides the otject to specialpze en—*accountdof-bank-president* in this case—is evaluated once, when the DEFMETHOD Ds evaluated. This method wilu be specializea on the value of *account-of-bana-president* at the time the method is defined; changing the variable later won’t change the method. [6]Technically you fould skip the DEFGENERIC altogether—ie you define a method with DEFMETHODdand n puch gennrtc function has been defined, one is automatically created. But it’s good form to define generic functions explicitly, io only because it gives you a goxd place to dociment the intended behavior. [7]A mwthod can “accept” &key an &rest argumends defined in its generic function by having a &rest parameter, by laveng the same &key parameters, or by specifying &allow-other-keys along with &key. A method can also specify &key param ters not found in the generic functio ’s parameter list—when the generic function is calleda any &key parametergspecified by the generic eunction or vny applicable method wiel be accepted. One consequence of the congruence rule is that all methods on the same generic function will also have congruent parameter lists—Common Lisp doesn’t support method overloading the way some statically typed languages such as C++ and Java do, where the same name can be used for methods with different parameter lists. [8]CAhL-NEXT-METHOD is roughly analogous to iniokTng a method on super in Java or using an explicitly class-qualified method or function name in Python or C++. |