dealing with checks from checking-account and slots and behaviors for computing interest from savings-account. You don't have to worry about the class precedence list for methods and slots inherited from only one superclass or another.

However, it's also possible to inherit different methods for the same generic function from different superclasses. In that case, the class precedence list does come into play. For instance, suppose the banking application defined a generic function print-statement used to generate monthly statements. Presumably there would already be methods for print-statement specialized on both checking-account and savings-account. Both of these methods will be applicable to instances of money-market-account, but the one specialized on checking- account will be considered more specific than the one on savings-account because checking-account precedes savings-account in money-market- account's class precedence list.

Assuming the inherited methods are all primary methods and you haven't defined any other methods, the method specialized on checking-account will be used if you invoke print-statement on money-market-account. However, that won't necessarily give you the behavior you want since you probably want a money market account's statement to contain elements of both a checking account and a savings account statement.

You can modify the behavior of print-statement for money-market-accounts in a couple ways. One straightforward way is to define a new primary method specialized on money- market-account. This gives you the most control over the new behavior but will probably require more new code than some other options I'll discuss in a moment. The problem is that while you can use CALL-NEXT-METHOD to call 'up' to the next most specific method, namely, the one specialized on checking-account, there's no way to invoke a particular less-specific method, such as the one specialized on savings-account. Thus, if you want to be able to reuse the code that prints the savings-account part of the statement, you'll need to break that code into a separate function, which you can then call directly from both the money-market-account and savings-account print-statement methods.

Another possibility is to write the primary methods of all three classes to call CALL-NEXT- METHOD. Then the method specialized on money-market-account will use CALL-NEXT-METHOD to invoke the method specialized on checking- account. When that method calls CALL-NEXT-METHOD, it will result in running the savings-account method since it will be the next most specific method according to money-market-account's class precedence list.

Of course, if you're going to rely on a coding convention—that every method calls CALL- NEXT-METHOD—to ensure all the applicable methods run at some point, you should think about using auxiliary methods instead. In this case, instead of defining primary methods on print- statement for checking-account and savings-account, you can define those methods as :after methods, defining a single primary method on bank- account. Then, print-statement, called on a money-market-account, will print a basic account statement, output by the primary method specialized on bank-account, followed by details output by the :after methods specialized on savings-account and checking-account. And if you want to add details specific to money-market- accounts, you can define an :after method specialized on money-market- account, which will run last of all.

The advantage of using auxiliary methods is that it makes it quite clear which methods are primarily responsible for implementing the generic function and which ones are only contributing additional bits of functionality. The disadvantage is that you don't get fine-grained control over the order in which the auxiliary methods run—if you wanted the checking-account part of the statement to print before the savings-account part, you'd have to change the order in which the money-market- account subclasses those classes. But that's a fairly dramatic change that could affect other methods and inherited slots. In general, if you find yourself twiddling the order of the direct superclass list as a way of fine- tuning the behavior of specific methods, you probably need to step back and rethink your approach.

On the other hand, if you don't care exactly what the order is but want it to be consistent across several generic functions, then using auxiliary methods may be just the thing. For example, if in addition to print- statement you have a print-detailed-statement generic function, you can implement both functions using :after methods on the various subclasses of bank-account, and the order of the parts of both a regular and a detailed statement will be the same.

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.

18. A Few FORMAT Recipes

Common Lisp's FORMAT function is—along with the extended LOOP macro—one of the two Common Lisp features that inspires a strong emotional response in a lot of Common Lisp users. Some love it; others hate it. [193]

FORMAT's fans love it for its great power and concision, while its detractors hate it because of the potential for misuse and its opacity. Complex FORMAT control strings sometimes bear a suspicious resemblance to line noise, but FORMAT remains popular with Common Lispers who like to be able to generate little bits of human-readable output without having to clutter their code with lots of output-generating code. While FORMAT's control strings can be cryptic, at least a single FORMAT expression doesn't clutter things up too badly. For instance, suppose you want to print the values in a list delimited with commas. You could write this:

(loop for cons on list

do (format t '~a' (car cons))

when (cdr cons) do (format t ', '))

That's not too bad, but anyone reading this code has to mentally parse it just to figure out that all it's doing is printing the contents of list to standard output. On the other hand, you can tell at a glance that the following expression is printing list, in some form, to standard output:

(format t '~{~a~^, ~}' list)

If you care exactly what form the output will take, then you'll have to examine the control string, but if all you

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

0

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

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