http://www.perlmonks.org?node_id=94783
Category: Miscellaneous
Author/Contact Info dkubb
Description:

Class::Flyweight is a module allows you to simply use the flyweight pattern in your modules. The major advantage to using this pattern is that the object data is not stored inside the blessed object itself. There is no way for a user of your module to access data in your object, unless you have provided the interface.

I am submitting this module for a code review, as I'm close to uploading it to CPAN. Before that I'd to get the monks' feedback and opinions before I take it to the next step.

In particular I am not sure of the following:

  • Do you think people will use it?
  • What about the method names? Something just doesn't feel comfortable about them. Especially clone(). Any naming suggestions?
  • Where should I beef up/trim down/improve the documentation?
  • Do you like the interface, and is it simple enough to not be limiting?
package Class::Flyweight;

=head1 NAME

Class::Flyweight - implement the flyweight pattern in OO perl 

=head1 SYNOPSIS

  package MyPackage;

  use strict;
  use Class::Flyweight;

  #Create the flyweight object store
  my $flyweight = Class::Flyweight->new;

  #make a new flyweight object
  sub new {
    my $class = shift;
    my $self  = $flyweight->clone({}, $class);

    return $self;
  }

  #fetch the data structure from the object 
  #store, then access it directly. 
  sub get_name {
    my $self = shift;
    my $data = $flyweight->fetch($self);

    return $data->{name};
  }

  #this is required to ensure proper clean-up 
  sub DESTROY { 
    my $self = shift;
    $flyweight->delete($self); 
  }

  1;

=head1 ABSTRACT

This module provides a lightweight
framework for developing perl modules
that have private internal data
structures.  It should be impossible
to modify an object's data stucture in 
ways not intended you, the author, thus 
providing true encapsulation of object 
data. 

=head1 DESCRIPTION

=head2 What is the flyweight pattern?

The flyweight pattern is a little known
technique for implementing objects.  The
main difference between the flyweight 
pattern and a normal object, is that the
blessed object is very small, and does not
carry around the object information.  Rather 
it acts like an "index" and knows how to get 
at the object data, but can only do so from 
within the lexical scope that we define the 
Class::Flyweight object in.

The key is that the object data storage is
done inside the scope that the class and
it's methods are defined in. Only the class
author can access the data, and provide
interfaces for others to do the same. It
*should* be impossible to get at this data
any other way, thus providing true object
encapsulation.

The concept was first introduced to me in 
Damian Conway's amazing book, Object Oriented 
Perl2.  Class::Flyweight is based on his examples 
on page 302, and encapsulating the functionality
he illustrates, hiding the details of accessing
the object store from you. 

=head2 How do I use it?

To begin using the flyweight pattern, you must
create a new flyweight object, using the c<new>
constructor.  Make sure that this is done inside
the perl module you are writing.

Next, make sure that all your methods pull
the object data from the data store using the
C<fetch> method.  It will return a valid reference
to the data inside the object.  Please make sure
that you do not allow direct access to modify the
object data from your methods.  Otherwise, that
defeats the purpose of Class::Flyweight, and it
would be better and simpler at that point to use 
pure arrays/hashes/etc.

One final thing you need to remember is to
make a DESTROY method for your class.  It
will be responsible for calling the C<delete>
method to ensure clean-up of the object data
when the object goes out of scope.   

Normally perl's garbage collection will clean 
out an object once the number of variables
referencing it has dropped to 0.  In this
system, the object and the object's data are
not tied directly.  Perl's garbage collection
facility has no way of knowing it needs to 
clean the data out unless you tell it to
explicitly though using the C<delete> method. 

The Synopsis contains a package that shows 
a complete working example. 

=cut

use strict;
use Carp;

use vars qw($VERSION);
$VERSION = '0.01';

#Internal Method.  Ensure that any objects created
#with this class are cleaned up properly.
sub DESTROY {
  my $self  = shift;

  #by the time DESTROY is called, we must
  #not have any objects left.
  croak 'usage: must do $flyweight->delete($self) in $self->DESTROY'
    if %$self;
}


=head1 METHODS

=over 4

=item * C<new>

  my $flyweight = Class::Flyweight->new;

The C<new> contructor is responsible
for creating a new flyweight object.

=cut

sub new {
  my $class = shift;
  return bless {}, $class;
}


=item * C<clone>

  my $new_object = $flyweight->clone($reference);
  my $new_object = $flyweight->clone($reference, $class);

The C<clone> method will create a new object
blessed into $class, that allows you to access
the data in the $flyweight data store. 

The second argument is a reference to a perl data
type. Any perl reference can be used. 

=cut

sub clone {
  @_ >= 2 or croak 'usage: $flyweight->clone(OBJECT,[CLASS])';

  my $self = shift;

  my $index;
  $index = rand 
    until $index and not exists $self->{$index};

  $self->{$index} = shift; 

  return bless \$index, shift || scalar caller;
}


=item * C<fetch>

  my $data_ref = $flyweight->fetch($self);

The C<fetch> method returns a reference to 
the object data inside the object store.  
The reference can be any valid reference, 
and is not limited to hashes or arrays.  Any 
object reference can be retrieved from the 
object store.

=cut

sub fetch {
  @_ == 2 or croak 'usage: $flyweight->fetch(OBJECT)';

  my $self  = shift;
  my $index = shift;
  return $self->{$$index};
}


=item * C<delete>

  $flyweight->delete($self);

The C<delete> method is responsible for cleaning
out the $self object from the data store, once 
it has gone out of scope.  

It must be called, before destruction, either by
the class author, or ideally in the classes
DESTROY method. 

=cut

sub delete {
  @_ == 2 or croak 'usage: $flyweight->delete(OBJECT)';

  my $self  = shift;
  my $index = shift;

  #decrement the reference count, so the object
  #data is cleaned up properly
  delete $self->{$$index};
}


=item * C<each>

  while(my $object = $flyweight->each) {
    #... do something with/to each object ...
  }

The C<each> Class method will iterate and
return each object in the flyweight object 
store.

=cut

sub each {
  wantarray and croak 'usage: each() only returns 1 argument';

  my $self     = shift;
  my $next_key = each %$self;

  return bless \$next_key, scalar caller
    if defined $next_key;
}

=pod

=back

=head1 BUGS

None that I know of.  Please let me know if you 
find anything, or have any suggestions.

=head1 AUTHOR

Dan Kubb <dkubb@cpan.org>. Vancouver, BC, Canada.

Intitial revision July 7, 2001.

=head1 ACKNOWLEDGEMENTS 

=over 4

=item *

A special thanks goes out to Damian Conway for his
excellent book, Object Oriented Perl, which first
introduced me to the flyweight concept, among many 
others.

=item *

The folks at perlmonks.org for providing and
contributing to a forum where I have learned more
about perl than at any other time since I first
typed #!/usr/bin/perl.

=back

=head1 COPYRIGHT

Class::Flyweight is free software; you can redistribute
it and/or modify it under the terms of the "Perl 
Artistic License".

=head1 SEE ALSO

=head2 Object Encapsulation Made Easy

  See the following essay by Damian Conway, that
  explains how flyweight objects work, and provides
  several other alternatives to providing object 
  encapsulation. 

    http://www.csse.monash.edu.au/~damian/TPC/1999/Encapsulation/Paper
+.html

=cut

1;