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 "name" as a lexical ("my") hash
property name => 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 };
}

LIMITATIONS AND ROADMAP

This is an alpha release for a work in progress. It is functional but incomplete and should not be used for any production purpose. It has been released to solicit peer review and feedback.

WARNING: Version 0.08 introduces a BACKWARDS INCOMPATIBLE syntax change to the property method. property now requires two arguments, including a label for the property. This label will be used in future versions to better support introspection and accessor creation.

Serialization with Storable appears to be working but may have unanticipated bugs and could use some real-world testing. Property destruction support for various inheritance patterns (e.g. diamond) is experimental and the API may change. There is minimal argument checking or other error handling. A future version will also add very basic accessor support.

DESCRIPTION

This is 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 does not require derived classes to subclass it; uses no source filters, 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.

It provides the minimal support necessary for creating safe inside-out objects. All other implementation details, including writing a constructor and managing inheritance, are left to the user.

Programmers seeking a more full-featured approach to inside-out objects are encouraged to explore Object::InsideOut. Other implementations are briefly noted in the "See Also" section.

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 for mod_perl or the use of source filters for a 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 the optional id alias to Scalar::Util::refaddr).

  • register must be called on all new objects

All other implementation details, including constructors, initializers and class inheritance management are left to the user. This does requires some additional work, but maximizes freedom. Class::InsideOut is intended to be a base class providing only fundamental features. Subclasses of Class::InsideOut could be written that build upon it to provide particular styles of constructor, destructor and inheritance support.

USAGE

Importing Class::InsideOut

use Class::InsideOut;

By default, Class::InsideOut imports three critical methods into the namespace that uses it: DESTROY, STORABLE_freeze and STORABLE_thaw. 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 DESTROY and STORABLE_* will still be imported even without an explicit request. This 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. Users seeking custom destruction behavior should consult "Object destruction" and the description of the DEMOLISH method.

Declaring and accessing object properties

Object properties are declared with the property function, which must be passed a label and a lexical (i.e. my) hash.

property name => my %name;
property age => my %age;

If users do not wish to import property, properties may be declared using a fully qualified syntax:

Class::InsideOut::property name => my %name;

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 to 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 automatically exports a customized DESTROY function. This function cleans up object property memory for all declared properties the class and for all Class::InsideOut based classes in the @ISA array 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.

# Sample DEMOLISH: Count objects demolished (for whatever reason)

my $objects_destroyed;

sub DEMOLISH {
  $objects_destroyed++;
}

DEMOLISH will only be automatically called if it exists for an object's class. DEMOLISH will not be inherited and DEMOLISH will not be called automatically for any superclasses.

DEMOLISH should manage any necessary calls to superclass DEMOLISH methods. As with new, implementation details are left to the user based on the user's approach to object inheritance. Depending on how the inheritance chain is constructed and how DEMOLISH is being used, users may wish to entirely override superclass DEMOLISH methods, rely upon SUPER::DEMOLISH, or may prefer to walk the entire @ISA tree:

use Class::ISA;

sub DEMOLISH {
  my $self = shift;
  # class specific demolish actions

  # DEMOLISH for all parent classes, but only once
  my @demolishers = map { $_->can("DEMOLISH") }
                        Class::ISA::super_path( __PACKAGE__ );
  for my $d ( @demolishers  ) {
    $d->($self) if $d;
  }
}

Generally, any class that inherits from another should define its own DEMOLISH method.

Foreign inheritance

Because inside-out objects built 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, without regard for 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 IO::File object would be, without interfering with any of its own inside-out functionality.

Classes using foreign inheritance should provide a DEMOLISH function that calls the foreign class destructor explicitly.

Serialization

Class::InsideOut has experimental support for serialization with Storable by providing the STORABLE_freeze and STORABLE_thaw methods. Storable will use these methods to serialize. They should not be called directly. Due to limitations of Storable, this serialization will only work for objects based on scalars, arrays or hashes.

References to object within the object being frozen will result in clones upon thawing unless the other references are included in the same freeze operation. (See Storable for details.)

 # assume $alice and $bob are objects
 $alice->friends( $bob );
 $bob->friends( $alice );

 $alice2 = Storable::dclone( $alice );

 # $bob was cloned, too, thanks to the reference 
 die if $alice2->has_friend( $bob ); 
 
 # get alice2's friend
 ($bob2) = $alice2->friends(); 
 
 # preserved relationship between bob2 and alice2
 die unless $bob2->has_friend( $alice ); 

User feedback on serialization needs and limitations is encouraged.

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. (See perlmod for details.)

Class::InsideOut itself has a CLONE function that automatically fixes up properties in a new thread to reflect the new memory addresses for all classes created with Class::InsideOut. register must be called on all newly constructed inside-out objects to register them for use in Class::InsideOut::CLONE.

Users are strongly encouraged not to define their own CLONE functions as they may interfere with the operation of Class::InsideOut::CLONE and leave objects in an undefined state. Future versions may support a user-defined CLONE hook, depending on demand.

Note: 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 (e.g. Class::InsideOut are not fork-safe for Win32 on Perl 5.6. Win32 Perl 5.8 fork is supported.

FUNCTIONS

property

property name => my %name;

Declares an inside-out property. Two arguments are required. The first is a label for the property; in a future version, this label will be used for introspection and generating accessors and thus should be a valid perl identifier. The second argument must be the lexical hash that will be used to store data for that property. Note that 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.

SEE ALSO

Other modules on CPAN

  • Object::InsideOut -- This is perhaps the most full-featured, robust implementation of inside-out objects currently on CPAN. It is highly recommended if a more full-featured inside-out object builder is needed. Its array-based mode is faster than hash-based implementations, but foreign inheritance is handled via delegation, which imposes certain limitations.

  • Class::Std -- Despite the name, this 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. Not thread-safe.

  • 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 safe for any production use and offers very weak encapsulation.

References

Much of the Perl community discussion of inside-out objects has taken place on Perlmonks (http://perlmonks.org). My scratchpad there has a fairly comprehensive list of articles (http://perlmonks.org/index.pl?node_id=360998). Some of the more informative articles include:

BUGS

Please report bugs or feature requests using the CPAN Request Tracker. Bugs can be sent by email to bug-Class-InsideOut@rt.cpan.org or submitted using the web interface 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 AND LICENSE

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.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.