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;
|
Re (tilly) 1: Class::Flyweight - implement the flyweight pattern in OO perl
by tilly (Archbishop) on Jul 09, 2001 at 07:27 UTC
|
Interesting idea.
I share your concern over the "clone" method. I would be
inclined to name the two main methods "instantiate" and
"access". So your example would look like:
package MyPackage;
use strict;
use Class::Flyweight;
my $flyweight = Class::Flyweight->new();
sub new {
return $flyweight->instantiate(bless {}, shift);
}
sub get_name {
my $self = $flyweight->access(shift);
return $self->{name};
}
etc. I would also export a private DESTROY method to
do the cleanup. There should be no reason for classes using
this to rewrite DESTROY all of the time by default.
But while I like the idea, I look at it and I have to
wonder what you have really added to what you get from
hand-rolling:
package MyPackage;
use strict;
my %instance;
sub new {
my $class = shift;
my $obj = {};
my $name = "$obj";
$instance{$name} = $obj;
return bless $name, class;
}
sub get_name {
my $self = $instance{shift(@_)};
return $self->{name};
}
etc. Well you have gained something, but I am not sure
that the overhead of adding a method call to every method
call is enough for me.
The direction that I would be inclined to go instead is to
make your encapsulation weaker. Instead of exporting
methods that can be used to implement a flyweight, accept
a hash of package translations and implement each class in
terms of the translated one, with a shared lexical scope.
That way the flyweight is implemented by having an
autogenerated class proxy for a private implementation.
This way someone could develop with encapsulation, and
then before making the code production could change their
package names and drop the flyweight protection for
speed.
This would involve writing and evaling code which would
wrap methods with AUTOLOAD. The protection would be less
(after all someone could easily take a look at your actual
package names). But it would still provide something and
it now is clear to me what value is added over handrolling
my own flyweight implementation (better performance in
production).
A note. The question was asked of how to handle
inheritance. Well yours handles that by allowing you to
explicitly copy the private flyweight object to each
inherited class. My suggestion would take more thought
on your part. Perhaps the private packages when you set
up the flyweight mapping will get treated specially, so
the only thing that changes when you go to production is
that you eliminate the flyweight and change package
names.
As for your other question, I think this is neat but I
am not sure that it is solving a pressing problem for me.
So I probably wouldn't use it. But I would put this in
my list of neat things to keep in mind just in case, even
though I don't have a specific use in mind right now... | [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Class::Flyweight - implement the flyweight pattern in OO perl
by simonm (Vicar) on Jul 09, 2001 at 02:30 UTC
|
Do you like the interface, and is it simple enough to not be limiting?
Yes, that's a really clean implementation of this pattern, and in nicely idiomatic Perl.
Do you think people will use it?
That's hard to tell -- as Conway notes, this approach is underappreciated, and modules that make it easier may or may not push it over the tipping point of widespread acceptance... But it'd be nice if they did!
Something just doesn't feel comfortable about ... clone()
Looking at the code, what it does is store() some data and give you back the flyweight index. Calling it store() also fits in with fetch(), delete, and each(), all standard names for hash-like operators.
Where should I beef up/trim down/improve the documentation?
At times it's not immediately clear whether "the flyweight object" in the documentation is referring to the central Flyweight hash or to the individual objects; it might help to call them "the storage object" and "the index objects" or something like that.
It's probably worth noting in the "How do I use it?" section that in order to obtain the privacy benefits, the flyweight storage object should be held in a file lexical variable rather than a package variable.
The only part I was unclear about was the subclassing strategy -- if I'm using a Class::Flyweight to store private data for MyBank::Tx, with various subclasses like MyBank::Tx::Deposit, MyBank::Tx::LoanPmt, and so forth, is there some way to "stack on" the new accessors the subclasses will want to declare?
FWIW, that's the concern that drove me to adopt a one-hash-per-accessor approach in Class::MakeMethods::Template::Flyweight. As you noted in your modules@perl.org posting, that module is still sketchy, and the data is not fully private. To be honest, I've basically been ignoring the encapulation angle, and using it more for the ability to add arbitrary data to existing objects, like reconnect attributes for a DBI handle, without messing around with the tied-hash internals...
Anyway, again -- nice module, and it's good to see more explicit discussion of software patterns and other highbrow engineering concepts in a Perl context.
| [reply] [Watch: Dir/Any] |
Re: Class::Flyweight - implement the flyweight pattern in OO perl
by Anonymous Monk on Jul 09, 2001 at 22:58 UTC
|
I don't see very much Flyweight going on here. You may want to
call your package Class::Privacy. Explanation: You store a scalar
per instance, provided the instance stores a scalar too. No space
saved. The Idea of Flyweight is: Many instances store common values
(mostly arrays and hashes (or records in other languages)),that are related,
as one constant value represented as a pointer (or ref) thereby
saving memory, provided they don't modify the pointed to value.
Modifyability is achieved by not only storing the pointer but overriding
values in the instance too in the hope that many values are common and
only a few must be overridden. An instance can modify its value by
changing the pointer (and possibly choosing appropriate overrides) too.
NOTE: The pointer is not the constant, it's the pointed to value that's constant. | [reply] [Watch: Dir/Any] |
|
|