Abstract classes define the interface, not the implementation,
of an object. There are no direct instances of an abstract class.
Concrete classes actually implement their interfaces. Every abstract
class will typically have one or more concrete subclasses. For example,
if plain vanilla vehicles shouldn't exist, <vehicle>
could be defined as follows:
define abstract class <vehicle> (<object>)
// ...as before
end;
The above modification prevents the creation of direct instances
of <vehicle>. At the moment, calling
make on this class would result in an error.
However, a programmer could add a method to make which allowed the
intelligent creation of vehicles based on some criteria, thus making
<vehicle> an instantiable abstract
class:
define method make(class == <vehicle>,
#rest keys, #key big? (#f), #all-keys)
=> <vehicle>;
if ( big? )
make( <truck>, keys, tons: 2 );
else
make( <car>, keys );
end;
end;
A number of new features appear in the parameter list. The
expression “class == <vehicle>”
specifies a singleton, one particular object
of a class which gets treated as a special case. Singletons are
discussed in the chapter on
Multiple Dispatch. The use of #rest,
#key and #all-keys in the same
parameter list accepts any and all keywords, binds one of them to
big? and places all of them into the variable
keys. The new make method could be invoked in
any of the following fashions:
let x = 1000000;
make(<vehicle>, sn: x, big?: #f); =>car
make(<vehicle>, sn: x, big?: #t); =>truck
make(<vehicle>, sn: x); =>car
Methods added to make don't actually need
to create new objects. Dylan officially allows them to return existing
objects. This can be used to manage lightweight shared objects, such
as the “flyweights” described by Gamma, et al., in
Design Patterns
.