NAME
Class::LazyObject - Deferred object construction
SYNOPSIS
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
DESCRIPTION
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.
WHY
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.
Example
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!
"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.
SETUP
You need to create a lazy object class for each regular class you want to inflate to.
- 1. Create a class to hold lazy objects that inflate to a particular class.
-
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. - 2. Make the class inherit from Class::LazyObject.
-
package Bob::Class::LazyObject; our @ISA = 'Class::LazyObject';
- 3. Do some configuration
-
Call
Class::LazyObject->inherit()
.It takes a series of named parameters (a hash). The only two required parameters are
deflated_class
andinflated_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. - 4. Create an inflation constructor in the inflated class
-
In the class that the lazy object will inflate to, define a class method
new_inflate
. This is called with a single parameter, the ID passed toClass::LazyObject->new
when this particular lazy object was created. (If no ID was passed,undef
is passed tonew_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 justnew
is so that you can writenew
to return lazy objects, unbeknownst to its caller.
That's all it takes to set up a lazy object class.
CLASS METHODS
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 that the lazy object inflates to (or any class that inherits from the class).
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:
- deflated_class
-
Required. The package the lazy object should be in before inflating, in other words, the class that's calling
inherit
. You should almost always just set this to__PACKAGE__
. - inflated_class
-
Required. The package the lazy object should inflate into.
- inflate
-
Optional. Takes a reference to a subroutine. This subroutine will be called when the lazy object inflates. See "THE INFLATE SUB" for more information. This allows you to override the default inflation behavior. By default, when a lazy object inflates,
Inflated::Class->new_inflate
is called and passed the lazy object's ID as an argument.
OBJECT METHODS
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.
THE INFLATE SUB
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.
Class::LazyObject VS. Object::Realize::Later
Chances are, if you have a problem that needs to be solved, there's a CPAN module that already solves it. Class::LazyObject was conceived and implemented before I knew about Object::Realize::Later. While the two modules solve the same type of problem, they do so in very different ways.
Have a look at the Object::Realize::Later documentation. Whichever module seems to make more sense to you is the one you should use.
Philosophy
Both modules help you to implement objects that act as stubs- that is, they do not provide the full functionality of a particular object, but automatically turn themselves into that kind of object when that functionality is needed. There are two approaches to creating such objects.
The first approach is to have an object that can exist in two states: deflated and inflated. Each of the object's methods must check which state the object is in, and, if necessary, call a method that inflates the object.
Object::Realize::Later automates this process. You define a class for the deflated object that contains only the methods that can work on the deflated object. Object::Realize::Later provides this class with an AUTOLOAD
that catches calls to all the methods that can only handle an inflated object, and calls a user-defined method to inflate that object. For more detail on this, see "About lazy loading" in Object::Realize::Later.
In the second approach, the deflated object and inflated object are really separate. The deflated object is a blessed scalar that contains the information necessary to construct the inflated object. The inflated object isn't even constructed at all until it is needed. When a method is called on the deflated object, it passes the scalar to an inflation subroutine that uses the data in it to call the constructor on the inflated object. In other words, the deflated object is really just a scalar that knows what object to transform itself into when methods are called on it.
Class::LazyObject automates creating a class for deflated objects that inflate to a particular other class. The main focus of Class::LazyObject is that deflated objects should have as small a memory footprint as possible, and that you should be able to easily graft lazy objects onto an existing program with little modification (a constructor can return a deflated object rather than an inflated object, and everything will still work as expected). (Note that Object::Realize::Later also allows you to have separate deflated and inflated objects, but leaves more of the work to you.)
Because these approaches are so different, they lend themselves to two unique ways of solving a problem that requires lazy objects. Rather than being duplications of effort, they carry on the Perl tradition of offering More Than One Way To Do It.
Invisibility
Each module differs in how it exposes the fact that an object is a lazy object.
Class::LazyObject treats lazy object-ness as an implementation detail, and attempts to make it impossible to tell whether an object is a lazy object or not. A deflated object has the same interface as an inflated object, and acts exactly the same. The only way to actually tell that an object is deflated is to do ref($object)
; but what class an object belongs to is also considered an implementation detail (as opposed to what the class inherits from or what the object's interface is). The thinking is that your code should never need to worry about whether an object is inflated or deflated, and should simply let Class::LazyObject take care of all of the lazy loading details.
Additionally, great pains have been taken in Class::LazyObject to keep the lazy object namespace free of object methods. You can call any method on a deflated object and it will be correctly passed on to the inflated class.
Object::Realize::Later takes a slightly different approach. Lazy loading is considered a feature of an object, and appropriate details are exposed to allow code to take advantage of this. Code designed to take an inflated object will work just fine with a deflated one (deflated objects are given can
and isa
methods that return the same things as if they had been called on inflated objects). However, the methods forceRealize
and willRealize
are also added to the deflated object class. They allow code to force an object to inflate, and to check what class an object will inflate to. These are considered features of all lazy objects, the same way can
and isa
are features of all objects.
Importing vs. Inheritance
Another difference is how methods are added to your deflated object class. Object::Realize::Later adds several methods directly to your class's namespace. (See "Added to YOUR class" in Object::Realize::Later.) This means that you cannot easily extend their functionality or override them, though there is admittedly probably very little need to do so in most cases (except possibly AUTOLOAD
).
Deflated object classes that use Class::LazyObject are subclasses of Class::LazyObject and inherit their functionality, meaning that they can be easily extended through the usual object-oriented means. As a downside to this method, however, calls to methods on deflated objects are probably a little slower.
Inflation when calling can and isa
With both modules, $deflated_object->can
and $deflated_object->isa
return the same thing either of these calls on an inflated object would return. Class::LazyObject does this by treating those like any other methods and inflating the object. Object::Realize::Later accomplishes this without inflating the object.
I plan to add functionality to Class::LazyObject at some point to let the user decide whether or not can
and isa
should inflate a deflated object or not. Until then, however, Object::Realize::Later is much better in this regard.
Maturity
Object::Realize::Later has existed for about two years longer than Class::LazyObject and has gone through more than a dozen revisions. It has also been used in the excellent Mail::Box suite as well as in other programs.
Class::LazyObject is a new module, still being developed. It will mature over time. However, if you want a stable, proven module right now, go with Object::Realize::Later.
IMPLEMENTATION
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.
CAVEATS
The ID cannot be an object of the same class as the inflated class or any class that inherits from the inflated class.
The 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.
Explicitly calling AUTOLOAD
on a lazy object may not do what you expect.
If you never explicitly 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 hierarchy 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.
BUGS
(The difference between bugs and caveats is that I plan to fix the bugs.)
Inheriting from lazy objects
Currently, you cannot easily inherit from a class that inherits from Class::LazyObject
. This will be fixed very soon.
Objects with overload
ed operators
Currently, 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.
Objects implementing tie
d datatypes
Class::LazyObject
has not yet been tested with objects that implement 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.
AUTHOR
Daniel C. Axelrod, daxelrod@cpan.org
SEE ALSO
Object::Realize::Later
Another module for creating lazy objects. See "Class::LazyObject VS. Object::Realize::Later" for a comparison between the two modules.
http://perlmonks.org/index.pl?node_id=279940
Fergal Daly had the idea for lazy objects before I did. Note that I had the idea independently, but subsequently discovered his posting.
COPYRIGHT
Copyright (c) 2003-2004, 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.
The full text of the license can be found in the LICENSE file included with this module.