NAME
Class::EHierarchy - Base class for traditional OO Objects
MODULE VERSION
$Id: EHierarchy.pm,v 0.6 2003/02/18 23:09:03 acorliss Exp acorliss $
SYNOPSIS
package Foo;
use Class::EHierarchy qw(:all);
use vars qw(@ISA);
@ISA = qw(Class::EHierarchy);
sub _init {
. . .
}
regObject($custom);
deregObject($custom);
package main;
$obj = Foo->new(
PROPERTIES => {
Name => 'Bar',
Value => 'I have no value!',
},
FLAGS => {
ReadOnly => 1,
Changed => 0,
});
$obj = Foo->new(
Name => 'Bar',
Value => 'I have no value!',
ReadOnly => 1,
Changed => 0,
);
@objects = listObjects;
$objref = getObject('Bar');
$rv = $obj->hasFlag('ReadOnly');
$f = $obj->flag('ReadOnly');
$f = $obj->flag('ReadOnly', 0);
$rv = $obj->checkState(qw(OR ReadOnly Changed));
$rv = $obj->hasProperty('Value');
$v = $obj->property('Value');
$rv = $obj->property('Value', 'Now I do!');
$objref = $obj->parent;
@childrefs = $obj->children;
$ns = $obj->namespace;
$obj->addChild($custom);
$obj->delChild($custom);
$obj->getChild('Custom Foo');
$obj->terminate;
REQUIREMENTS
Nothing outside of core Perl modules.
DESCRIPTION
This module provides a base class for traditional OO objects. As such, it is not intended for use directly, only as exposed through derived or subclassed modules.
This module aggregates three OO class traits into a single module. It does not implement some of these traits completely, focusing instead on specific aspects (see the various subsections in the Introduction).
Modules which subclass this module will inherit Properties and state Flags, similar in usage to other language implementations. In addition, class functions provide for tracking and access of objects currently in memory. All objects can track internally parent/child relationships and do a clean termination of object heirarchies automatically.
INTRODUCTION
Some usage of traditional nomenclature need some clarification. In this document the terms Parent and Child refer not to the normal class ancestral ties but to the container relationship. In other words, children belong to their parent, and that parent is responsible for cleaning up after them when they're all destroyed.
That said, this class provides three core capabilities for all subclasses:
Automatic Accessors
There are two main elements within an object that you gain automatic accessors for: Properties and Flags. Both can be accessed via a self-named virtual method:
# Retrieve the state of the read-only flag
$rv = $obj->ReadOnly;
# Retrieve the name of the object
$name = $obj->Name;
In the case that both a flag and a property share the same name the virtual method will always work on the property. The only way to access the flag at this point would be with the flag method:
# Equivalent call as above
$rv = $obj->flag('ReadOnly');
Properties have an equivalent call, in those rare cases that you have a method of the same name that doesn't do property manipulation (please don't interpret the presence of this method as encouragement for more API obsfucation ;-):
# Equivalent call as above
$name = $obj->property('Name');
Flags differ from properties in that they are strictly boolean (Perlish booleans, anyway, either 1 or 0). In addition, each flag can be tied to an "event handler", which is a method which is called each time the flag is accessed (yes, that's right, accessed, not modified). Flags don't require a developer-provided accessor method since the value of the flag is read and written directly to the register.
Properties have the extra capability of being write-only, read-only, or read-write. The mutability of a property is defined by the associated accessor methods listed for that property in the PROPERTIES hash. These are installed by the _init method, which is called by the new constructor. It is expected that each subclassed module override the _init method appropriately.
Accessor methods can be defined in one of four ways:
sub _init {
my $self = shift;
my $properties = $self->{PROPERTIES};
. . .
# Write-only example
$$properties{'Value'} = [\&_wValue, undef];
# Read-only example
$$properties{'Value'} = [undef, \&_rValue];
# Read-Write w/separate methods per access mode
$$properties{'Value'} = [\&_wValue, \&_rValue];
# Unified read-write handler
$$properties{'Value'} = \&_rwValue;
. . .
}
As a convenience, a generic property accessor method is provided as part of this class (_genPropAccessor) which can be used in any of the above modes. Custom accessors should accept the same calling list that the generic accessor does:
$self->_customAccessor($property, @values);
Since we always pass the property being accessed as the first value, you can write generic accessors that can handle access for several properties. A values list is not needed, of course, for read-only accessors.
Write and unified accessors should always return a boolean value for attempted write operations. The return value should designate whether or not the write operation succeeded.
NOTE: The generic accessor assumes that all property values are stored in the PROPVALUES hash:
# Retrieving the value of the Value property
$value = $self->{PROPVALUES}->{Value};
It also attempts to do the Right Thing, depending on the reference type of the hash value. If it's a hash or array, it dereferences the value and returns it in a list fashion. If it's a scalar or any other kind of reference (code, object, or scalar reference, etc.) it returns the value directly.
During assignment multiple values are assigned only if the hash value is an array or hash reference. Otherwise, it assumes that you're doing an assignment to a scalar value, and stores the number of values passed, not the values themselves.
Understanding this is important: if you have a property that stores a list of values, you must initialise that hash value with, at minimum, an empty array reference. If you don't, you will be forced to make the initial assignment by passing a value list by reference, instead of as normal arguments to the method. The same holds true for hash properties. Also note that assigning a list to an array or hash property overwrites the previous contents. This method does not combine or aggregate them.
Regardless of whether or not you use the generic accessor method, it would be wise to keep with the internal convention and store all of your property values in the PROPVALUES hash space, and not the object hash space itself.
Rudimentary Event Handlers
A rudimentary event system exists based on the Flag register. Flags, as mentioned above, are essentially boolean properties, but properties that you associate event handlers for in lieu of accessors. Any associated event handler is called with each access or modification to that flag. Event handlers are associated with specific flags in the _init method:
sub _init {
my $self = shift;
my $flags = $self->{FLAGS};
. . .
%$flags = (
%$flags,
ReadOnly => \&_roHandler, # Flag w/event handler
Changed => undef, # Flag w/o event handler
);
. . .
}
Event handlers will be called with the following syntax and arguments (using the above ReadOnly example):
$obj->_rohandler($oldvalue, $newvalue);
The code withing the handler can decide whether to take action by comparing the old and new values of the flag.
# This handler only takes action when the flag changes
sub _rohandler {
my $self = shift;
my ($ovalue, $nvalue) = @_;
if ($ovalue != $nvalue) {
# do something
}
}
# This handler takes action whenever the flag is accessed/set and
# and is true
sub _rohandler {
my $self = shift;
my ($ovalue, $nvalue) = @_;
if ($nvalue) {
# do something
}
}
# This handler takes action whenever the flag is set to true
sub _rohandler {
my $self = shift;
my ($ovalue, $nvalue) = @_;
if ($nvalue && ! $ovalue) {
# do something
}
}
The thing to remember is that event handlers are called immediately upon register access, so you need to take care that you don't overflow the execution stack by getting stuck in a recursive calling loop (i.e., an event handler accesses a different flag which sets off an event handler that accesses the original flag, and sets off another event handler call, etc.).
Event handlers must return the final flag state as their return value.
Containers
The relationship implied by the container aspect of this class lies not in common ancestry or interface, but in ownership (i.e., an object exists only within the context of the container, once the container is destroyed, all contained objects are destroyed as well). The point of tracking this relationship is to do a more orderly destruction of objects than what Perl's normal garbage collection does.
Take database container object that contains table objects that cache writes. The container is responsible for maintaining the database connection, and uses a DESTROY method to close any existing connections cleanly. The table objects have a DESTROY method that flushes and changes to the data in memory to the database. If you were to rely on Perl's normal garbage collection, the container's last reference would go out of scope first, causing the database connection to close. Then, as the container's resources are freed, the last references to the table objects would close and they would subsequently try to flush their data to a closed connection.
For this reason, we need the container to make sure that it destroys the objects it contains before it destroys itself. And for that we have the terminate method:
$container->terminate;
Every subclass based on this class will recursively call terminate so that the container's subcontainers and objects are freed from the bottom up.
CLASS FUNCTIONS
regObject
$rv = regObject($obj);
NOTE: Any object class that uses the constructor method provided in this module should never have to call this function.
This function registers the creation of a new object with the class tracking hash. It will return a true or a false depending on whether registration was successful.
While each object must have a unique name, each object provides a private namespace for any children. As an example:
# Registered in the class tracking hash as 'Foo'
$parent = Class::EHierarchy::Derived->new(
PROPERTIES => { Name => 'Foo' });
# Registered in the class tracking hash as 'Foo::Bar'
$child = Class::EHierarchy::Derived->new(
PARENT => $parent,
PROPERTIES => { Name => 'Bar' },
);
Creating an object with the same name as another object in the same namespace will cause this function to return a false value.
This function is only exported as part of the :all export tag set.
deregObject
$rv = deregObject($obj);
NOTE: Any object class that uses the terminate method provided in this module should never have to call this function.
This function removes the object reference from the class tracking hash. It returns a true or false depending on whether the deregistration was successful.
This function is only exported as part of the :all export tag set.
listObjects
@objects = listObjects;
This function returns a list of all the full names of any objects registered with the class tracking hash. Each full name consists of a namespace and a name a la:
($namespace, $name) = ($fullname =~ /^(.+::)?(.+)$/);
These names will be returned in random hash key order.
getObject
$obj = getObject($fullname);
This function returns a reference to the object specified by its full name. If that object is not registered in the class tracking hash, an undef is returned.
METHODS
new
$obj = Class::EHierarchy::Derived->new(
PARENT => $pobj,
PROPERTIES => {
Name => 'foo',
},
FLAGS => {
ReadOnly => 1,
Changed => 0,
},
);
$obj = Class::EHierarchy::Derived->new(
PARENT => $pobj,
Name => 'foo',
ReadOnly => 1,
Changed => 0,
);
The new constructor instantiates an instance of the specified class. The only mandatory argument that needs to be passed is the Name definition within the PROPERTIES hash. The PARENT argument specifies the container (another object) that this object belongs to, and is optional.
As the above examples show, if all the passed values are truly unique (in other words, no flags, properties, or other special keys with the same name) you can omit the PROPERTIES and FLAGS hashes, putting the key/value pairs directly in the constructor argument list.
_init (OVERRIDE IN SUBCLASS)
$rv = $self->_init(%conf);
This method performs any subclass-specific initialisation needed, and is called from within the constructor. Use this method to define the flags, properties, and default values for that class. It should return a true or false, with the latter causing the constructor to not return a valid object reference.
It is important to remember not to wipe out any information placed in the PROPERTIES, PROPVALUES, FLAGS, and FLAGREGISTER hashes by the constructor. In other words, dereference the existing hash references, don't replace it with another one (at this time, only the Name property is being handled by the constructor).
Example:
=========================================
sub _init {
my $self = shift;
my %conf = @_;
my $properties = $self->{PROPERTIES};
my $propvals = $self->{PROPVALUES};
my $flags = $self->{FLAGS};
my $register = $self->{FLAGREGISTER};
# Define some additional properties
%$properties = (
%$properties,
# Separate routines for read and write access
Value => [ \&_wValue, \&_rValue],
# A read-only property using the generic accessor method
Foo => [ undef, \&_genPropAccessor],
# A read-write property using the generic accessor in unifed mode
Bar => \&_genPropAccessor,
);
# Use the conf values to set some default properties
foreach (keys %$properties) {
$$propvals{$_} = $conf{PROPERTIES}{$_} if exists $conf{PROPERTIES}{$_};
$$propvals{$_} = $conf{$_} if exists $conf{$_};
}
# Define some additional flags
%$flags = (
%$flags,
# Flag with no associated event handler
ReadOnly => undef,
# Flag with an associated event handler
Changed => undef,
);
# Set whatever default flags and properties you want. . .
$$register{ReadOnly} = 1;
return 1;
}
Please remember that the constructor allows the constructor to be passed a flat hash as an argument list in lieu of separating the pairs into PROPERTIES and FLAGS. You should try to accommodate that as long as there are no name collisions between the two.
flag
$value = $obj->flag('ReadOnly');
$value = $obj->flag('ReadOnly', 1);
The flag method provides access to the flag register, and returns the value of the flag. The first argument is the name of the flag, while the second optional argument is the value it is to be set to.
Please note that you can access flags in the same manner as properties provided that there is no property with the same name:
$value = $obj->ReadOnly;
$value = $obj->ReadOnly(1);
checkState
$rv = $obj->checkState(qw(OR ReadOnly Changed));
This method returns the logical result of the specified operation and the list of flags. You can choose from the following list of operators: AND, OR, XOR.
hasFlag
$rv = $obj->hasFlag('Foo');
This method returns a boolean value designating whether or not a flag with the given name exists.
property
$value = $obj->property('Value');
$rv = $obj->property('Value', 1);
This method provides access to defined properties. The first argument is the name of the property to access, while the second optional argument is the new value to assign to the property. Write operations do not return the property value, they return a boolean value designating whether or not the attempted write operation was successful.
Please note that you can access all properties as a virtual method, provided no other method exists with that name.
$value = $obj->Value;
$rv = $obj->Value(1);
hasProperty
$rv = $obj->hasProperty('Foo');
This method returns a boolean value designating whether or not a property with the given name exists.
parent
$objref = $obj->parent;
This method returns a reference to the container (or parent) of the object. This will be undef for those objects that don't belong to any particular container.
children
@children = $obj->children;
This method returns a list of object references to every object (or child) contained by this object. The list will be empty for any childless object.
namespace
$ns = $obj->namespace;
This method returns a scalar string containing the container this object belongs to. For parentless objects, this will be a zero-length string.
addChild
$obj->addChild($objref);
This method is not typically used by the developer, since the constructor of this call automatically calls it upon successful initialisation of an object. If, however, you want a non-Class::EHierarchy-derived module to be contained by this object, this method is available.
delChild
$obj->delChild($objref);
Like the addChild method, this is not normally used by the developer, since the terminate method calls it automatically.
getChild
$objref = $obj->getChild('Foo');
This method returns an object reference for the specified child, or undef if no child by that name exists.
_genPropAccessor
As mentioned in the INTRODUCTION, this method is provided as a convenience for the class developer, and is not intended to be called directly by class users. Please read the Automatic Accessors subsection for a more complete description of the usage of this method.
terminate
$obj->terminate;
This method should be called prior to letting your last object reference go out of scope. Failure to do so will cause memory leaks and other such badness to happen.
can
$obj->$method if $obj->can($method);
This module does override the UNIVERSAL::can method to check for property and flags that the AUTOLOAD method may have not created a permanent method for yet. Since it uses and returns the result of the UNIVERSAL::can, there is no difference in usage.
HISTORY
AUTHOR/COPYRIGHT
(c) 2003, Arthur Corliss (corliss@digitalmages.com)