DESTROY
method does not cause inflation.use Class::LazyObject
after use
ing any module that puts subs in UNIVERSAL
.AUTOLOAD
on a lazy object may not do what you expect.overload
ed operatorsUNIVERSAL::isa
and UNIVERSAL::can
tie
d datatypes
Class::LazyObject - Deferred object construction
use Class::LazyObject; package Bob::Class::LazyObject; our @ISA = 'Class::LazyObject'; Class::LazyObject->inherit( deflated_class => __PACKAGE__, inflated_class => 'Bob' inflate => sub { my ($class, $id) = @_; return $class->new($id); } ); package main; my @bobs; foreach (0..10_000)#make 10 thousand lazy Bobs { push @bobs, Bob::Class::LazyObject->new($_); } # @bobs now contains lazy objects, not real Bobs. # No Bob objects have been constructed yet. my $single = $bobs[rand @bobs]; #rand returned 10 $single->string;#returns 10. #Single is now an actual Bob object. Only one #Bob object has been constructed. package Bob; #It's really expensive to create Bob objects. sub string { #return the scalar passed to ->new() } #other Bob methods here
Class::LazyObject allows you to create lazy objects. A lazy object holds the place of another object, (Called the ``inflated object''). The lazy object turns into the inflated object (``inflates'') only after a method is called on the lazy object. After that, any variables holding the lazy object will hold the inflated object.
In other words, you can treat a lazy object just like the object it's holding the place of, and it won't turn into a real object until necessary. This also means that the real object won't be constructed until necessary.
A lazy object takes up less memory than most other objects (it's even smaller than a blessed empty hash). Constructing a lazy object is also likely to be computationally cheaper than constructing an inflated object (especially if a database is involved).
A lazy object can hold a scalar (called the ``ID'') that is passed to the constructor for the inflated object.
Note that I believe I've coined the term ``lazy object''.
When would you want to use lazy objects? Any time you have a large number of objects, but you will only need to use some of them and throw the rest of them away.
For example, say you have a class Word
. A Word has a name, a part of speech,
and a definition. Word's constructor is passed a name, and then it fetches the
other information about the word from a database (which is a dictionary and so
has thousands of words). $word_object->others_with_this_pos()
returns an
array of all Words in the database with the same part of speech as $word_object.
If you only want to pick 4 words at random that have the same part of speech as
$word_object, hundreds of unnecessary Word objects might be created by
others_with_this_pos()
. Each of them would require information to be
retrieved from the database, and stored in memory, only to be destroyed when the
array goes out of scope.
It would be much more efficient if others_with_this_pos()
returned an array
of lazy objects, whose IDs were word names. Lazy objects take up less memory
than Word objects and do not require a trip to the database when they are
constructed. The 4 lazy objects that are actually used would turn into Word
objects automatically when necessary.
``But wait,'' you say, ``that example doesn't make any sense!
others_with_this_pos()
should just return an array of word names. Just pass
these word names to Word
's constructor!''
Well, I don't know about you, but I use object orientation because I want to be able to ignore implementation details. So if I ask for words, I want Word objects, not something else representing Word objects. I want to be able to call methods on those Word objects.
Class::LazyObject
lets you have objects that are almost as small as scalars
holding the word names. These objects can be treated exactly like Word objects.
Once you call a method on any one of them, it suddenly is a word object.
Better yet, you don't have to know about any of this to use the lazy Word
objects. As far as you know, they are word objects.
You need to create a lazy object class for each regular class you want to inflate to.
package Bob::Class::LazyObject;
Note that a package whose name is your package name with ::Methods appended
(Bob::Class::LazyObject::Methods
for this example) is also automatically
created by Class::LazyObject, so don't use a package with that name for
anything.
package Bob::Class::LazyObject; our @ISA = 'Class::LazyObject';
Class::LazyObject->inherit()
.
It takes a series of named parameters (a hash). The only two required parameters
are deflated_class
and inflated_class
. See inherit for more
information.
package Bob::Class::LazyObject; our @ISA = 'Class::LazyObject'; Class::LazyObject->inherit( deflated_class => __PACKAGE__, inflated_class => 'Bob' );
When you call Class::LazyObject->inherit()
, Class::LazyObject sets some
class data in your lazy object class.
new_inflate
. This is called with a single parameter, the ID passed to
Class::LazyObject->new
when this particular lazy object was created. (If
no ID was passed, undef
is passed to new_inflate
.) This method should be a
constructor for your class. It must return an object of the inflated class, or
of a class that inherits from the inflated class. (Unless the object isa the
inflated class, bad things will happen.)
If you wish to have the inflation constructor be named something other than
new_inflate
, or want it to be called in different way, see
THE INFLATE SUB.
The reason new_inflate
is called by default rather than just new
is so
that you can write new
to return lazy objects, unbeknownst to its caller.
That's all it takes to set up a lazy object class.
Now that you've set up a lazy object class (if you haven't, see SETUP), how do you actually make use of it?
The methods here are all class methods, and they must all be called on a class
inherited from Class::LazyObject
. If you want to know about object methods
instead, look at OBJECT METHODS.
new
new(ID) new()
Class::LazyObject->new
takes one optional scalar parameter, the object's
ID. This ID is passed to the inflation constructor when the lazy object
inflates.
Note that the ID cannot be an object of the same class (or any class that inherits from the class) that the lazy object inflates to.
inherit
inherit(deflated_class => __PACKAGE__, inflated_class => CLASS) inherit(deflated_class => __PACKAGE__, inflated_class => CLASS, inflate => CODE); #Optional
Class::LazyObject->inherit
should only be called by any class that
inherits from Class::LazyObject. It takes a hash of named arguments. Only the
deflated_class
and inflated_class
arguments are required. The arguments
are:
inherit
. You should almost always just set
this to __PACKAGE__
.
Inflated::Class->new_inflate
is called and passed
the lazy object's ID as an argument.
None, except an AUTOLOAD that catches calls to any other methods.
Calling any method on a lazy object will inflate it and call that method on the inflated object.
You should pass a reference to a sub as the value of the inflate
parameter of
the inherit
class method. This sub is called when the lazy
object needs to be inflated.
The inflate sub is passed two parameters: the name of the class to inflate into, and the ID passed to the lazy object's constructor.
The inflate sub should return a newly constructed object.
If you supply an inflate parameter to inherit, you override the default inflate sub, which is:
sub {my ($class, $id) = @_; return $class->new_inflate($id);}
But you could define your inflate sub to do whatever you want.
A lazy object is a blessed scalar containing the ID to be passed to the inflation constructor. AUTOLOAD is used to intercept calls to methods. When a method is called on a lazy object, it calls the inflation constructor on the neccesary class, and sets $_[0] to the newly created object, replacing the lazy object with the full object. The full object is also stored in the blessed scalar, so that if any other variables hold references to the lazy object, they can be given the already created full object when they call a method on the lazy object.
Additional chicanery takes place so that calls to methods inherited from
UNIVERSAL
are intercepted, and so that AUTOLOAD
ed methods of the inflated
object are called correctly.
DESTROY
method does not cause inflation.There's no way (either that, or it's very difficult) to tell whether the
DESTROY
method has been explicitly invoked on a lazy object, or whether Perl
is just trying to destroy the object. It is, however, unlikely that you would
need to explicitly call DESTROY
on any of your objects anyway. I may later
add capability to change this behavior.
use Class::LazyObject
after use
ing any module that puts subs in UNIVERSAL
.Class::LazyObject
has to do extra work to handle calls on lazy objects to
methods defined in UNIVERSAL
. It does this work when you use
Class::LazyObject
. Therefore, if you add any subs to UNIVERSAL
(with
UNIVERSAL::exports
, UNIVERSAL::moniker
, or UNIVERSAL::require
, for
example), only use Class::LazyObject
afterwards.
AUTOLOAD
on a lazy object may not do what you expect.If you never explictly call $a_lazy_object->AUTOLOAD
, this caveat does
not apply to you. (Calling AUTOLOAD
ed methods, on the other hand, is fine.)
If you set $AUTOLOAD in a package with a hardcoded value (because you think you
know in which package the AUTOLOAD sub is defined for a particular class) and
then call $a_lazy_object->AUTOLOAD
, the object will inflate, but a
different method will be called on the inflated object than you intended. If
you're trying to spoof calls to AUTOLOAD, you should really be searching through
the inheritance heirarchy of the object (with the help of something like
Class::ISA) until you find the package that the object's AUTOLOAD method is
defined in, and then set that package's $AUTOLOAD. (In fact, Class::LazyObject
does this kind of AUTOLOAD search itself.)
I will most likely revise this caveat to make more sense.
(The difference between bugs and caveats is that I plan to fix the bugs.)
overload
ed operatorsCurrently, lazy objects will not intercept overloaded operators. This means that
if your inflated object uses overloaded operators, you cannot use a lazy object
in its place. This may be fixed in future versions by using a combination of
nomethod
and overload::Method
. See overload to learn more about
overloaded operators.
UNIVERSAL::isa
and UNIVERSAL::can
Currently, UNIVERSAL::isa($a_lazy_object,
'Class::The::Lazy::Object::Inflates::To')
is false, though
$a_lazy_object->isa
will do the right thing. Similarly,
UNIVERSAL::can($a_lazy_object, 'method')
won't work like it's supposed to,
but $a_lazy_object->can
will work correctly. This may be fixed in a
future release.
tie
d datatypesClass::LazyObject
has not yet been tested with objects that impliment tie
d
datatypes. It may very well work, and then again, it may not. Explicit support
may be added in a future release. See perltie to learn more about tie
s.
Daniel C. Axelrod, daxelrod@cpan.org
Fergal Daly had the idea for lazy objects before I did. Note that I had the idea independently, but subsequently discovered his posting.
Copyright (c) 2003, Daniel C. Axelrod. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.