An "Appendage" would be a class extension orthogonal to its normal
inheritance hierarchy. An appendage would be defined using normal class
syntax only replacing the colon in the definition with an equal sign:
# A normal class.
class Base {
int i;
void getData() { return i; }
}
# An appendage on Base.
class Appendage = Base {
void f() { ... }
void g() { ... }
}
Appendages defined this way always have a single base class. Wherever an
appendage is required, the base class can be used and it will be implicitly
converted to the appendage:
void doSomething(Appendage a) {
# Call appendage methods.
a.f();
a.g();
# Call Base methods.
i = a.getData();
}
doSomething(Base()); # Base automatically converts to Appendage.
At runtime (and without conversion checking, discussed below) the conversion
from the base class to one of its appendages has zero cost because the
instance representation of a base class and its appendage are identical
(hence the equal sign). The conversion process is simply pointer conversion.
Any class derived from the base will also convert to the appendages for that
base. For example:
class Derived : Base { ... }
doSomething(Derived());
For a derived class, the cost of converting to an appendage is the same as the
cost of converting to the base.
Like classes, an appendage would provide a cast() method to facilitate use in
contexts where there is no implicit conversion: Appendage.cast(baseInst).f()
If an "oper onCast" method were defined in the appendage, it would be called
during conversion and could be used to verify that the state of the base
instance matches a set of constraints, throwing an exception otherwise:
class Appendage = Base {
void oper onCast() {
if (getData() > 100)
throw AssertionError('Base data too big!');
}
}
...
}
Appendages are subject to limitations:
- They can have no instance variables.
- They can have no virtual methods. All methods are implicitly final
(except, perhaps, for those that are explicitly static).
Appendages are essentially syntactic sugar for a collection of functions
accepting the base class as input. We would currently write the example above
as follows:
void f(Base b) { ... }
void g(Base b) { ... }
Appendages are better because:
- They can be imported as a complete set instead of independently. ("import
module Appendage" versus "import module f, g")
- They allow us to preserve the receiver.function() syntax in places where
it is appropriate.
- They allow you to organize a related set of functions into a namespace.
- They can be used to impose runtime constraints on an object's state,
facilitating Design By Contract.
- They can probably be made to support a generic mode (class A[T] = ...)
sooner than plain functions.
I have identified multiple interfaces in the library that could benefit from
appendages. Examples I can remember offhand are:
- crack.game.sdlgfx. Most of the drawing methods are logically methods of
SDL_Surface as a first argument but they couldn't be bundled into a
Surface class because sdl and sdlgfx are both optional libraries that may not
both be installed.
- crack.ascii. This contains many functions that are more logically
represented as methods on a Buffer.
- crack.fieldset. Appendages would allow us to replace the Foo.get(context)
syntax with context.getFoo().
=============================================================================
michaelMuller =
mmu...@enduden.com |
http://www.mindhog.net/~mmuller
-----------------------------------------------------------------------------
There is no way to find the best design except to try out as many designs as
possible and discard the failures. - Freeman Dyson
=============================================================================