NAME
Class::InsideOut - a safe, simple inside-out object construction kit
SYNOPSIS
package My::Class;
use Class::InsideOut qw( property register id );
use Scalar::Util qw( refaddr );
# declare a lexical property hash with 'my'
property my %name;
sub new {
my $class = shift;
my $self = \do {my $scalar};
bless $self, $class;
# register the object for thread-safety
register( $self );
}
sub name {
my $self = shift;
if ( @_ ) {
# use 'refaddr' to access properties for an object
$name{ refaddr $self } = shift;
return $self;
}
return $name{ refaddr $self };
}
sub greeting {
my $self = shift;
# use 'id' as a mnemonic alias for 'refaddr'
return "Hello, my name is " . $name { id $self };
}
DESCRIPTION
This is an alpha release for a work in progress. It is a functional but incomplete implementation of a simple, safe and streamlined toolkit for building inside-out objects. Unlike most other inside-out object building modules already on CPAN, this module aims for minimalism and robustness.
It uses no source filters, no attributes or CHECK blocks, supports any underlying object type including foreign inheritance, does not leak memory, is overloading-safe, is thread-safe for Perl 5.8 or better and should be mod_perl compatible.
In its current state, it provides the minimal support necessary for safe inside-out objects. All other implementation details, including writing a constructor and accessors, are left to the user. Future versions will add basic accessor support and serialization support.
Inside-out object basics
Inside-out objects use the blessed reference as an index into lexical data structures holding object properties, rather than using the blessed reference itself as a data structure.
$self->{ name } = "Larry"; # classic, hash-based object
$name{ refaddr $self } = "Larry"; # inside-out
The inside-out approach offers three major benefits:
Enforced encapsulation: object properties cannot be accessed directly from ouside the lexical scope that declared them
Making the property name part of a lexical variable rather than a hash-key means that typos in the name will be caught as compile-time errors
If the memory address of the blessed reference is used as the index, the reference can be of any type
In exchange for these benefits, however, robust implementation of inside-out objects can be quite complex. Class::InsideOut
manages that complexity.
Philosophy of Class::InsideOut
Class::InsideOut
provides a minimalist set of tools for building safe inside-out classes with maximum flexibility.
It aims to offer minimal restrictions beyond those necessary for robustness of the inside-out technique. All capabilities necessary for robustness should be automatic. Anything that can be optional should be. The design should not introduce new restrictions unrelated to inside-out objects (such as attributes and CHECK
blocks that cause problems mod_perl
or the use of source filters for new syntax).
As a result, only a few things are mandatory:
Properties must be based on hashes and declared via
property
Property hashes must be keyed on the
Scalar::Util::refaddr
of the object (or theid
alias).register
must be called on all new objects
All other implementation details, including constructors, are left to the user.
USAGE
Importing Class::InsideOut
use Class::InsideOut;
By default, Class::InsideOut
imports two critical methods, CLONE
and DESTROY
. These methods are intimately tied to correct functioning of the inside-out objects. No other functions are imported by default. Additional functions can be imported by including them as arguments with use
:
use Class::InsideOut qw( register property id );
Note that CLONE
and DESTROY
will still be imported even without an explicit request. They can only be avoided by explicitly doing no importing, via require
or passing an empty list to use
:
use Class::InsideOut ();
There is almost no circumstance under which this is a good idea.
Declaring and accessing object properties
Object properties are declared with the property
function, which must be passed a single lexical (my
) hash.
property my %name;
property my %age;
Properties are private by default and no accessors are created. Users are free to create accessors of any style.
Properties for an object are accessed through an index into the lexical hash based on the memory address of the object. This memory address must be obtained via Scalar::Util::refaddr
. The alias id
is available for brevity.
$name{ refaddr $self } = "James";
$age { id $self } = 32;
In the future, additional options will be supported to create accessors in various styles.
Object construction
Class::InsideOut
provides no constructor function as there are many possible ways of constructing an inside-out object. Additionally, this avoids constraining users into any particular object initialization or superclass initialization approach.
By using the memory address of the object as the index for properties, any type of reference can be used as the basis for an inside-out object with Class::InsideOut
.
sub new {
my $class = shift;
my $self = \do{ my $scalar }; # anonymous scalar
# my $self = {}; # anonymous hash
# my $self = []; # anonymous array
# open my $self, "<", $filename; # filehandle reference
register( bless $self, $class );
}
However, to ensure that the inside-out objects are thread-safe, the register
function must be called on the newly created object. See register for details.
A more advanced technique uses another object, usually a superclass object, as the object reference. See "Foreign inheritance" for details.
Object destruction
Class::InsideOut
provides a DESTROY
function. This function cleans up object property memory for all declared properties to avoid memory leaks or data collision.
Additionally, if a user-supplied DEMOLISH
function is available in the same package, it will be called with the object being destroyed as its argument. DEMOLISH
can be used for custom destruction behavior such as updating class properties, closing sockets or closing database connections. Object properties will not be deleted until after DEMOLISH
returns.
my $objects_destroyed;
sub DEMOLISH {
$objects_destroyed++;
}
DEMOLISH
is also the place to manage any necessary calls to superclass destructors. As with new
, implementation details are left to the user based on the user's approach to object inheritance.
Foreign inheritance
Because inside-out objects build with Class::InsideOut
can use any type of reference for the object, inside-out objects can be built using other objects. This is of greatest utility when extending a superclass object. Most importantly, this works regardless of whether the superclass object is implemented with a hash or array or other reference.
use base 'IO::File';
sub new {
my ($class, $filename) = @_;
my $self = IO::File->new( $filename );
register( bless $self, $class );
}
In the example above, IO::File
is a superclass. The object is an IO::File
object, re-blessed into the inside-out class. The resulting object can be used directly anywhere an c<IO::File> object would be, without interfering with any of its own inside-out functionality.
Serialization
Serialization support with hooks for Storable has not yet been implemented.
Thread-safety
Because Class::InsideOut
uses memory addresses as indices to object properties, special handling is necessary for use with threads. When a new thread is created, the Perl interpreter is cloned, and all objects in the new thread will have new memory addresses. Starting with Perl 5.8, if a CLONE
function exists in a package, it will be called when a thread is created to provide custom responses to thread cloning.
Class::InsideOut
provides a CLONE
function that automatically fixes up properties in a new thread to reflect the new memory addresses. register
must be called on all newly constructed inside-out objects to register them for use in CLONE
.
Additionally, fork
on Perl for Win32 is emulated using threads since Perl 5.6. (See perlfork.) As Perl 5.6 did not support CLONE
, inside-out objects using memory addresses are not fork-safe for Win32.
FUNCTIONS
property
property my %name;
Declares an inside-out property. The argument must be a lexical hash, though the my
keyword can be included as part of the argument rather than as a separate statement. No accessor is created, but the property will be tracked for memory cleanup during object destruction and for proper thread-safety.
register
register $object;
Registers an object for thread-safety. This should be called as part of a constructor on a object blessed into the current package. Returns the object (without modification).
id
$name{ id $object } = "Larry";
This is a shorter, mnemonic alias for Scalar::Util::refaddr
. It returns the memory address of an object (just like refaddr
) as the index to access the properties of an inside-out object.
CLONE
CLONE
is automatically exported to provide thread-safety to modules using Class::InsideOut
. See perlmod for details. It will be called automatically by Perl if threads are in use and a new interpreter thread is created. It should never be called directly.
DESTROY
This destructor is automatically exported to modules using Class::InsideOut
to clean up object property memory usage during object destruction. It should never be called directly. DESTROY
will call a user-supplied DEMOLISH
method if one exists to allow for additional, custom destruction actions such as closing sockets or database handles. DEMOLISH
is called prior to deleting object properties.
SEE ALSO
Object::InsideOut -- This is perhaps the most full-featured, robust implementation of inside-out objects, but foreign inheritance is handled via delegation. Highly recommended if a more full-featured inside-out object builder is needed. Its array-based mode is faster than hash-based implementations.
Class::Std -- Despite the name, does not reflect best practices for inside-out objects. Does not provide thread-safety with CLONE, is not mod_perl safe and doesn't support foreign inheritance.
Class::BuildMethods -- Generates accessors with encapsulated storage using a flyweight inside-out variant. Lexicals properties are hidden; accessors must be used everywhere. Not thread-safe.
Lexical::Attributes -- The original inside-out implementation, but missing some key features like thread-safety. Also, uses source filters to provide Perl-6-like object syntax.
Class::MakeMethods::Templates::InsideOut -- Not a very robust implementation. Not thread-safe. Not overloading-safe. Has a steep learning curve for the Class::MakeMethods system.
Object::LocalVars -- My own original thought experiment with 'outside-in' objects and local variable aliasing. Not production-safe and offers very weak encapsulation.
BUGS
Please report bugs using the CPAN Request Tracker at http://rt.cpan.org/NoAuth/Bugs.html?Dist=Class-InsideOut
When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.
AUTHOR
David A. Golden (DAGOLDEN)
dagolden@cpan.org
http://dagolden.com/
COPYRIGHT
Copyright (c) 2006 by David A. Golden
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the LICENSE file included with this module.