Class::EHierarchy - Base class for hierarchally ordered objects
$Id: lib/Class/EHierarchy.pm, 2.01 2019/05/23 07:29:49 acorliss Exp $
package TelDirectory;
use Class::EHierarchy qw(:all);
use vars qw(@ISA @_properties @_methods);
@ISA = qw(Class::EHierarchy);
@_properties = (
[ CEH_PRIV | CEH_SCALAR, 'counter', 0 ],
[ CEH_PUB | CEH_SCALAR, 'first', '' ],
[ CEH_PUB | CEH_SCALAR, 'last', '' ],
[ CEH_PUB | CEH_ARRAY, 'telephone' ]
);
@_methods = (
[ CEH_PRIV, '_incrCounter' ],
[ CEH_PUB, 'addTel' ]
);
sub _initalize {
my $obj = CORE::shift;
my %args = @_;
my $rv = 1;
# Statically defined properties and methods are
# defined above. Dynamically generated
# properties and methods can be done here.
return $rv;
}
...
package main;
use TelDirectory;
my $entry = new TelDirectory;
$entry->set('first', 'John');
$entry->set('last', 'Doe');
$entry->push('telephone', '555-111-2222', '555-555'5555');
Class::EHierarchy is intended for use as a base class for objects that
need support for class or object hierarchies. Additional features are also
provided which can be useful for general property implementation and
manipulation.
Object relationships are often implemented in application code, as well as the
necessary reference storage to keep dependent objects in scope. This class
attempts to relive the programmer of that necessity. To that end, the concept
of an object hierarchy is implemented in this class.
An OOP concept for RDBMS data, for instance, could be modeled as a collection of
objects in the paradigm of a family tree. The root object could be your DBI
connection handle, while all of the internal data structures as child objects:
DBH connection
+-> views
| +-> view1
+-> tables
+-> table1
+-> rows
| +-> row1
+-> columns
Each type of object in the RDBMS is necessarily defined in context of the parent
object.
This class simplifies the formalization of these relationships, which can have a
couple of benefits. Consider a row object that was retrieved, for example. If
each of the columns was implemented as a property in the object one could
allow in-memory modification of data with a delayed commit. When the
connection goes out of scope you could code your application to flush those
in-memory modifications back to the database prior to garbage collection.
This is because garbage collection of an object causes a top-down destruction of
the object tree (or, in the depiction above, bottom-up), with the farthest
removed children reaped first.
Another benefit of defined object hierarchies is that you are no longer required
to keep track of and maintain references to every object in the tree. Only the
root reference needs to be tracked since the root can also act as an object
container. All children references can be retrieved at any time via method
calls.
An alias system is also implemented to make children retrieval even more
convenient. Each table, for instance, could be aliased by their table name.
That allows you to retrieve a table object by name, then, instead of iterating
over the collection of tables until you find one with the attributes you're
seeking.
Class hierarchies are another concept meant to allieviate some of the tedium of
coding subclasses. Traditionally, if you subclassed a class that required any
significant initialization, particularly if it relied on internal data
structures, you would be reduced to executing superclass constructors, then
possibly executing code paths again to account for a few changed properties.
This class explicitly separates assignment of properties from initialization,
allowing you to execute those code paths only once. OOP implementations of
mathematical constructs, for instance, could significantly alter the values
derived from objects simply by subclassing and overriding some property
values. The original class' initializer will be run once, but using the new
property values.
In addition to that this class provides both property and method
compartmentalization so that the original class author can limit the
invasiveness of subclasses. Both methods and properties can be scoped to
restrict access to both. You can restrict access to use by only the
implementation class, to subclasses, or keep everything publically available.
The class hierarchal features necessarily make objects derived from this class
opaque objects. Objects aren't blessed hashes, they are scalar references with
all properties stored in class data structures.
The property implementation was made to be flexible to accommodate most needs. A
property can be a scalar value, but it also can be an array, hash, or a number
of specific types of references.
To make non-scalar properties almost as convenient as the raw data structures
many core functions have been implemented as methods. This is not just a
semantic convenience, it also has the benefit of working directly on the raw
data stored in the class storage. Data structures aren't copied, altered, and
stored, they are altered in place for performance.
Functions and constants are provided strictly for use by derived classes within
their defined methods. To avoid any confusion all of our exportable symbols
are *not* exported by default. You have to specifically import the
all
tag set. Because these functions should not be used outside of the subclass
they are all preceded by an underscore, like any other private function.
The following constants are provided for use in defining your properties and
methods.
Scope
---------------------------------------------------------
CEH_PRIV private scope
CEH_RESTR restricted scope
CEH_PUB public scope
Type
---------------------------------------------------------
CEH_SCALAR scalar value or reference
CEH_ARRAY array
CEH_HASH hash
CEH_CODE code reference
CEH_GLOB glob reference
CEH_REF object reference
Flag
---------------------------------------------------------
CEH_NO_UNDEF No undef values are allowed to be
assigned to the property
You'll note that both
@_properties and
@_methods are arrays of arrays, which each subarray
containing the elements for each property or method. The first element is
always the attributes and the second the name of the property or method. In
the case of the former a third argument is also allowed: a default value for
the property:
@_properties = (
[ CEH_PUB | CEH_SCALAR, 'first', 'John' ],
[ CEH_PUB | CEH_SCALAR, 'last', 'Doe' ],
[ CEH_PUB | CEH_ARRAY, 'telephone',
[ qw(555-555-1212 555-555-5555) ] ],
);
Properties lacking a data type attribute default to
CEH_SCALAR. Likewise,
scope defaults to
CEH_PUB. Public methods can be omitted from
@_methods since they will be assumed to be public.
Methods only support scoping for attributes. Data types and flags are not
applicable to them.
$obj = new MyClass;
All of the hierarchal features require bootstrapping in order to work. For that
reason a constructor is provided which performs that work. If you wish to
provide additional initialization you can place a
_initialize method in
your class which will be called after the core bootstrapping is complete.
$rv = $obj->_initialize(@args);
The use of this method is optional, but if present it will be called during the
execution of the constructor. The boolean return value will determine if the
constructor is successful or not. All superclasses with such a method will be
called prior to the final subclass' method, allowing you to layer multiple
levels of initialization.
Initialization is performed
after the assignment of default values to
properties. If your code is dependent on those values this allows you the
opportunity to override certain defaults -- assuming they are visible to the
subclass -- simply by setting those new defaults in the subclass.
As shown, this method is called with all of the arguments passed to the
constructor, and it expects a boolean return value.
$child = MyClass->conceive($parent, @args);
conceive is an alternate constructor that's intended for those subclasses
with are dependent on relationships to parent objects during initialization.
$obj->DESTROY;
Object hierarchal features require orderly destruction of children. For that
purpose a
DESTROY method is provided which performs those tasks. If you
have specific tasks you need performed prior to the final destruction of an
object you can place a
_deconstruct method in your subclass.
$rv = $obj->_desconstruct;
_deconstruct is an optional method which, if present, will be called
during the object's
DESTROY phase. It will be called
after all
children have completed their
DESTROY phase. In keeping with the class
hierarchal features all superclasses will have their
_deconstruct
methods called after your subclass' method is called, but prior to finishing
the
DESTROY phase.
$rv = $obj->isStale;
It is possible that you might have stored a reference to a child object in a
tree. If you were to kick off destruction of the tne entire object tree by
letting the root object's reference go out of scope the entire tree will be
effectively destroyed. Your stored child reference will not prevent that from
happening. At that point you effectively have a stale reference to a
non-functioning object. This method allows you to detect that scenario.
The primary use for this method is as part of your safety checks in your
methods:
sub my_method {
my $obj = shift;
my @args = @_;
my $rv = !$obj->isStale;
if ($rv) {
# Do method work here, update $rv, etc.
} else {
carp "called my_method on a stale object!";
}
return $rv;
}
It is important to note that this method is used in every public method provided
by this base class. All method calls will therefore safely fail if called on a
stale object.
$rv = _declProp($obj, CEH_PUB | CEH_SCALAR | CEH_NO_UNDEF, @propNames);
This function is used to dynamically create named properties while declaring
their access scope and type.
Constants describing property attributes are OR'ed together, and only one scope
and one type from each list should be used at a time. Using multiple types or
scopes to describe any particular property will make it essentially
inaccessible.
NOTE: CEH_NO_UNDEF only applies to psuedo-scalar types like proper
scalars, references, etc. This has no effect on array members or hash values.
$rv = _declMethod(CEH_RESTR, @methods);
This function is is used to create wrappers for those functions whose access you
want to restrict. It works along the same lines as properties and uses the
same scoping constants for the attribute.
Only methods defined within the subclass can have scoping declared. You cannot
call this method for inherited methods.
NOTE: Since scoping is applied to the class symbol table (
not on a
per object basis) any given method can only be scoped once. That means you
can't do crazy things like make public methods private, or vice-versa.
$rv = $obj->adopt($cobj1, $cobj2);
This method attempts to adopt the passed objects as children. It returns a
boolean value which is true only if all objects were successfully adopted.
Only subclasses for Class::EHierarchy can be adopted. Any object that isn't
based on this class will cause this method to return a false value.
$rv = $obj->disown($cobj1, $cobj2);
This method attempts to disown all the passed objects as children. It returns a
boolean value based on its success in doing so. Asking it to disown an object
it had never adopted in the first place will be silently ignored and still
return true.
Disowning objects is a prerequisite for Perl's garbage collection to work and
release those objects completely from memory. The
DESTROY method
provided by this class automatically does this for parent objects going out of
scope. You may still need to do this explicitly if your parent object manages
objects which may need to be released well prior to any garbage collection on
the parent.
$parent = $obj->parent;
This method returns a reference to this object's parent object, or undef if it
has no parent.
@crefs = $obj->children;
This method returns an array of object references to every object that was
adopted by the current object.
@descendents = $obj->descendents;
This method returns an array of object references to every object descended from
the current object.
@crefs = $obj->siblings;
This method returns an array of object references to every object that shares
the same parent as the current object.
$root = $obj->root;
This method returns a reference to the root object in this object's ancestral
tree. In other words, the senior most parent in the current hierarchy.
$rv = $obj->alias($new_alias);
This method sets the alias for the object, returning a boolean value. This can
be false if the proposed alias is already in use by another object in its
hierarchy.
$ref = $obj->getByAlias($name);
This method returns an object reference from within the object's current object
hierarchy by name. It will return undef if the alias is not in use.
$rv = $obj->set('FooScalar', 'random text or reference');
$rv = $obj->set('FooArray', @foo);
$rv = $obj->set('FooHash', %foo);
This method provides a generic property write accessor that abides by the
scoping attributes given by
_declProp or
@_properties. This means that basic reference types are
checked for during assignment, as well as flags like
CEH_NO_UNDEF.
$val = $obj->get('FooScalar');
@val = $obj->get('FooArray');
%val = $obj->get('FooHash');
This method provides a generic property read accessor. This will return an undef
for nonexistent properties.
@properties = $obj->properties;
This method returns a list of all registered properties for the current object.
Property names will be filtered appropriately by the caller's context.
$rv = $obj->push($prop, @values);
This method pushes additional elements onto the specified array property. It
returns the return value from the
push function, or undef on
non-existent properties or invalid types.
$rv = $obj->pop($prop);
This method pops an element off of the specified array property. It returns the
return value from the
pop function, or undef on non-existent properties
or invalid types.
$rv = $obj->unshift($prop, @values);
This method unshifts additional elements onto the specified array property. It
returns the return value from the
unshift function, or undef on
non-existent properties or invalid types.
$rv = $obj->shift($prop);
This method shifts an element off of the specified array property. It returns
the return value from the
shift function, or undef on non-existent
properties or invalid types.
$rv = $obj->exists($prop, $key);
This method checks for the existence of the specified key in the hash property.
It returns the return value from the
exists function, or undef on
non-existent properties or invalid types.
@keys = $obj->keys($prop);
This method returns a list of keys from the specified hash property. It returns
the return value from the
keys function, or undef on non-existent
properties or invalid types.
$obj->merge($prop, foo => bar);
$obj->merge($prop, 4 => foo, 5 => bar);
This method is a unified method for storing elements in both hashes and arrays.
Hashes elements are simply key/value pairs, while array elements are provided
as ordinal index/value pairs. It returns a boolean value.
@values = $obj->subset($hash, qw(foo bar) );
@values = $obj->subset($array, 3 .. 5 );
This method is a unified method for retrieving specific element(s) from both
hashes and arrays. Hash values are retrieved in the order of the specified
keys, while array elements are retrieved in the order of the specified ordinal
indexes.
$obj->remove($prop, @keys);
$obj->remove($prop, 5, 8 .. 10);
This method is a unified method for removing specific elements from both hashes
and arrays. A list of keys is needed for hash elements, a list of ordinal
indexes is needed for arrays.
NOTE: In the case of arrays please note that an element removed in the
middle of an array does cause the following elements to be shifted
accordingly. This method is really only useful for removing a few elements at
a time from an array. Using it for large swaths of elements will likely prove
it to be poorly performing. You're better of retrieving the entire array
yourself via the
property method, splicing what you need, and calling
property again to set the new array contents.
$rv = $obj->empty($name);
This is a unified method for emptying both array and hash properties. This
returns a boolean value.
None.
The notion and portions of the implementation of opaque objects were lifted from
Damian Conway's
Class::Std(3) module. Conway has a multitude of great
ideas, and I'm grateful that he shares so much with the community.
Arthur Corliss (
[email protected])
This software is licensed under the same terms as Perl, itself. Please see
http://dev.perl.org/licenses/ for more information.
(c) 2017, Arthur Corliss (
[email protected])