That's a very different approach to how I was thinking about it. You're saying that BaseClass should control all of the inheritance, vs. each class doing the inheritance.
I'm not looking for technical solutions right now, though. I appreciate them, and I know that's how most people (including me) tend to think, which is why I want to shy away from them for now. I want to create a contract. Something that determines how someone will use BaseClass, regardless of how it's implemented. (I already have two implementations for objects, and I know there have to be more than that!) Maybe, something like this:
BaseClass will do the following:
- Support a define_attributes() function. This will have the following attributes:
- It will set the attributes that exist in the current class.
- It will look in that class's @ISA to find all parent classes (if any) and include those classes's attributes.
- It will be call-able from the package level, in the form Foo->define_attributes(). This will define attributes for the class.
- It will call-able from the object level, in the form $self->define_attributes(). These will define attributes for the class.
- define_attributes() will take a hash of hashes, in the form { attribute => { Type => $val1, Default => $val2 }.
- $val1 will be a listref containing which types are legal for this attribute.
- NUMBER will require that the value have only digits within it. ref will return undef.
- SCALAR will allow any alphanumeric scalar. No references. ref will return undef.
- HASHREF will allow any reference to a hash as determined by ref.
- LISTREF will allow any reference to a list as determined by ref.
- SCALARREF will allow any reference to a scalar, as determined by ref.
- REFERENCE will allow any reference, as determined by ref.
- OBJECT will allow any reference to a class, as determined by ref.
- Foo::Bar will allow any reference to the class Foo::Bar, as determined by ref.
- Foo::* will allow any reference to a class matching that string, as determined by ref.
- $val2 will be a value of a legal type for this attribute, as checked by $val1, and will be the value copied into the attribute whenever a new object is instantiated.
- Support a new() constructor.
- new() will be call-able from the package level, in the form Foo->new($val1).
- new() will return a bless'ed reference to an object of class Foo.
- $val1, if it exists, will be a hash (or hashref), where the keys are pre-defined attribute names and the values are the values for those keys.
- new() will call a function called initialize() as its last action prior to returning the blessed reference, passing it $val1.
- Support an initialize function. This is meant to be overloaded, should there be class-specific initializing actions. Naming attributes and setting default values should be done using define_attributes().
- The BaseClass initialize() function will solely set the values of all attributes passed into the call to new().
- Every overload of initialize() should call $self->SUPER::initialize(@_), to make sure that the parent class(es) do their initial setup work, if any.
- Support a clone() function. This will be called in the form $self->clone.
- clone() will return a blessed reference to the class of $self.
- All values in the original will be copied to the given attribute in the copy.
- If a value is a reference, then the values referred to will be copied, not the reference. This rule will be recursive, so as to deal with lists of hashes of objrefs, etc.
- Support a number of functions that will act as accessors and/or maintenance functions. All of these functions will take a list(ref) or hash(ref), as necessary, and return a scalar or list, depending on calling context.
These functions will be called in the form $self->func($val1);
- exists() will take a list(ref) of attribute names and return the type-list of an attribute, if it exists within the class, or undef.
- get() will take a list(ref) of attribute names and return the value stored in that attribute, if it exists within the class, or undef.
- set() will take a hash(ref) of attribute names and values. It will validate that the given value matches the allowable type(s) for that attribute and, if it does, set the attribute to the new value. It will return 1 if that attribute exists and the value was allowable and 0 otherwise.
Update:Added clone() after reading Damian Conway's book. (Forgot about copying!)