From: Phil Stubblefield
Subject: Re: How make an abstract class in CLOS?
Date: 
Message-ID: <392AC266.557546EE@rpal.rockwell.com>
Sergio <········@teleline.es> wrote:
> 
> How can i make an abstract class using defclass?

Other posters have already addressed some of the issues, specifically
that CLOS does not support the definition of abstract classes in the
same way that Java does (unless, of course, you use the MOP to define
the behavior yourself).  For example, it takes more than a single
keyword to enforce the desire that all subclasses of a given class
implement a specific method.  In fact, as David pointed out, CLOS
methods do not "belong" to a class in the same way that Java or C++
methods do, so you must adjust your thinking accordingly.

However, you can certainly communicate the *intent* of an abstract
class, which can be just as important as enforcing compile-time
behavior.  For example, suppose you want to defined an abstract class
named SHAPE, each of whose subclasses must implement the methods AREA
and PERIMETER.  The following code is fairly simple, but effectively
communicates the author's intent to the reader.  It does not enforce
compile-time behavior, but will signal a meaningful run-time error if
a required method is absent from a particular subclass.

--------------------------------- snip ---------------------------------

(in-package "CL-USER")


;;; The abstract class SHAPE.

(defclass shape ()
  ()
  (:documentation "An abstract shape.

                   This class is not intended to be instantiated."))

(defun make-shape (class &rest initargs)
  "Creates and returns a shape having the given characteristics."
  ;; Could add type-checking code here....
  (apply #'make-instance class initargs))

(defgeneric area (shape)
  (:documentation "Returns the area of the given shape.")
  (:method ((shape shape))
           "This default method signals an error."
           (error "The required method 'AREA' is missing from ~
                    the class '~S'."
                  (class-name (class-of shape)))))

(defgeneric perimeter (shape)
  (:documentation "Returns the perimeter of the given shape.")
  (:method ((shape shape))
           "This default method signals an error."
           (error "The required method 'PERIMETER' is missing from ~
                    the class '~S'."
                  (class-name (class-of shape)))))


;;; The concrete subclass SQUARE.
;;;
;;; Note that this class defines the required methods by hand.

(defclass square (shape)
  ((side
    :reader side
    :initarg :side
    :documentation "The length of one side of this square.")))

(defmethod area ((square square))
  "Returns the area of the given square."
  (let ((side (side square)))
    (* side side)))

(defmethod perimeter ((square square))
  "Returns the perimeter of the given square."
  (* 4 (side square)))


;;; The concrete subclass CIRCLE.
;;;
;;; Note that this class defines the required methods using slot
;;; readers.

(defclass circle (shape)
  ((radius
    :reader radius
    :initarg :radius
    :documentation "The length of the radius of this circle.")
   (area
    :reader area
    :documentation "The area of this circle.")
   (perimeter
    :reader perimeter
    :documentation "The perimeter of this circle.")))

(defmethod initialize-instance :after ((circle circle) &key)
  (with-slots (radius area perimeter) circle
    (setq area (* pi radius radius))
    (setq perimeter (* 2 pi radius))))

--------------------------------- snip ---------------------------------

Using these definitions:

USER(13): (setq s (make-shape 'square :side 1))
#<SQUARE @ #x225d298a>
USER(14): (area s)
1
USER(15): (perimeter s)
4
USER(16): (setq c (make-shape 'circle :radius 1))
#<CIRCLE @ #x225d3682>
USER(17): (area c)
3.141592653589793
USER(18): (perimeter c)
6.283185307179586


Some appropriate macros and utility functions could clean up this
example and prevent unnecessary duplication of code so that you
could, for example, write

(define-abstract-method area (shape))

which would expand into a DEFGENERIC form as shown above.  In fact, you
could take this idea to its logical conclusion by defining a macro that
allows you to write

(define-abstract-class shape ()
  (:required-method area (shape))
  (:required-method perimeter (shape)))

and have it expand into a PROGN containing the appropriate forms.

However, please permit me to make an observation based on personal
experience.  If you have the free time, then by all means take the
time to define DEFINE-ABSTRACT-METHOD and DEFINE-ABSTRACT-CLASS,
expanding their capabilities as necessary to meet your needs.  You'll
probably learn some invaluable lessons about macros and about CLOS.
Then, after you've defined the macros to your satisfaction -- consider
throwing most of your work away!

Why would you do that?  Well, before maintaining or enhancing your
code, the next programmer who comes along must first understand it.
If your code uses your own high-level abstractions, then such a
programmer must digest those definitions *before* even beginning to
understand your domain-specific code.  For example, most readers will
understand the general intent of DEFINE-ABSTRACT-CLASS, but unless
you've put a lot of thought into the issues, specific behaviors may be
very unexpected.  For example, can an abstract class also define
normal slots?  What happens when one abstract class inherits from
another?  What if a third class inherits from two different abstract
classes, each of which defines a required method having the same name
but different lambda lists?  And so on, ad infinitum.  If you as the
programmer use DEFCLASS, DEFGENERIC, and DEFMETHOD forms, then I can
usually decipher these interactions without undue effort.  But if you
use DEFINE-ABSTRACT-CLASS, then I must understand your implementation
in some detail before modifying your code.

OTOH, high-level abstractions often encapsulate difficult pieces of
code and help to reduce clutter.  As a recent poster said in another
thread, they can hide the crufty details in order to emphasize the
structure of the problem at hand.  You as the author must decide when
and where to use them.  But always keep in mind the reader of your
code -- which may be yourself several months or years from now!


Phil Stubblefield
Rockwell Palo Alto Laboratory                               206/655-3204
http://www.rpal.rockwell.com/~phil                ····@rpal.rockwell.com