Method Combination

33.1 Standard Method Combination: before, after, around

Most specific first, outermost Must call call-next-method to continue Most specific first Return values ignored

Most specific first; call-next-method goes to the next Provides the return value Least specific first

(defclass account ()
  ((balance :initarg :balance :initform 0 :accessor balance)))

(defgeneric withdraw (account amount))

(defmethod withdraw ((acc account) amount)          ; primary method
  (decf (balance acc) amount))

(defmethod withdraw :before ((acc account) amount)  ; runs before the primary methods
  (when (> amount (balance acc))
    (error "Insufficient funds: balance ~a, requested ~a" (balance acc) amount)))

(defmethod withdraw :after ((acc account) amount)   ; runs after
  (format t "~&Withdrew ~a, new balance ~a~%" amount (balance acc)))

(defmethod withdraw :around ((acc account) amount)  ; wraps everything
  (format t "~&[audit] starting withdrawal~%")
  (let ((result (call-next-method)))                ; continue to before/primary/after
    (format t "~&[audit] done~%")
    result))

(withdraw (make-instance 'account :balance 100) 30)
;; [audit] starting withdrawal
;; Withdrew 30, new balance 70
;; [audit] done

33.2 Built-in and Custom Method Combinations

(defgeneric total-price (item) (:method-combination +))   ; sum the results of all applicable methods
(defclass product () ())
(defclass taxed (product) ())
(defmethod total-price + ((p product)) 100)
(defmethod total-price + ((p taxed)) 18)
(total-price (make-instance 'taxed))       ; 118

(defgeneric describe-features (obj) (:method-combination list))
(defmethod describe-features list ((p product)) :basic)
(defmethod describe-features list ((p taxed)) :taxed)
(describe-features (make-instance 'taxed)) ; (:TAXED :BASIC)

;; Other built-in combinations: progn, and, or, append, nconc, max, min
;; define-method-combination creates your own (short or long form)
(defun concatenate-strings (&rest strings) (apply #'concatenate 'string strings))
(define-method-combination concatenate-strings)   ; short form: the operator defaults to the same name
(defgeneric label (obj) (:method-combination concatenate-strings))
(defmethod label concatenate-strings ((p product)) "product")
(defmethod label concatenate-strings ((p taxed)) "taxed-")
(label (make-instance 'taxed))             ; "taxed-product"