NAME

Class::EHierarchy - Base class for hierarchally ordered objects

VERSION

$Id: lib/Class/EHierarchy.pm, 2.01 2019/05/23 07:29:49 acorliss Exp $

SYNOPSIS

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');

DESCRIPTION

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 HIERARCHIES

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 implmented 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

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 implemenations 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.

ADDITIONAL FEATURES

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 accomodate 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.

CONSTANTS

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.

SUBROUTINES/METHODS

new

$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.

_initialize

$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.

conceive

$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.

DESTROY

$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.

_deconstruct

$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 thier 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.

isStale

$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.

_declProp

$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.

_declMethod

$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.

adopt

$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.

disown

$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

$parent = $obj->parent;

This method returns a reference to this object's parent object, or undef if it has no parent.

children

@crefs = $obj->children;

This method returns an array of object references to every object that was adopted by the current object.

descendents

@descendents = $obj->descendents;

This method returns an array of object references to every object descended from the current object.

siblings

@crefs = $obj->siblings;

This method returns an array of object references to every object that shares the same parent as the current object.

root

$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.

alias

$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.

getByAlias

$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.

set

$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.

get

$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

@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.

push

$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.

pop

$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.

unshift

$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.

shift

$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.

exists

$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

@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.

merge

$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.

subset

@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.

remove

$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.

empty

$rv = $obj->empty($name);

This is a unified method for emptying both array and hash properties. This returns a boolean value.

DEPENDENCIES

None.

BUGS AND LIMITATIONS

CREDIT

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.

AUTHOR

Arthur Corliss (corliss@digitalmages.com)

LICENSE AND COPYRIGHT

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 (corliss@digitalmages.com)