Object::Trampoline
 Why having not the object you want 
is just what you need.
Steven Lembark
Workhorse Computing
lembark@wrkhors.com
We've all been there
Starting the web server crashes your database.
Testing requires working handles for services you don't test.
Compiling error messages in 15 languages you never use.
Patterns: Ways to Scratch an Itch
Language-specific, idiomatic solutions.
Meta-data about language strengths, limitations.
Example: C patterns are about pointers and string handling.
Perl patterns here: objects, dispatch, and stack manglement.
Pattern: “Lazy Initialization”
Separate “construction” and “initialization”.
You only need to construct an object to dispatch with it.
Delay initialization until methods are called.
Common way: If-logic
Construct a naked object.
Check object contents within every method, overload, ...
Works until someone forgets to check...
… or benchmarks the overhead of checking.
Other ways:
Use a factory class to construct a new object every time.
Hard to maintain state across calls.
Use a cached connection (e.g., DBI::connect_cached).
Expensive: check what you know to be true every time.
Not lazy or impatient.
Pattern: Trampoline Class
Simplest approach: co-operating classes with re-bless.
Construct the object in one class.
Bless it into the “bounce class”.
Bounce class initializes the object.
Re-bless the object where it belongs.
Warning:Warning:
Code shown here containsCode shown here contains
graphicgraphic AUTOLOAD's, AUTOLOAD's,
un­filtered bless,un­filtered bless,
& hard­wired stack.& hard­wired stack.
Parenthetical Parenthetical discretiondiscretion is advised. is advised.
Example: Multi-stage initialization
package Foo;package Foo;
## new calls the constructor, re-blesses thenew calls the constructor, re-blesses the
## object then stores the initialize data.object then stores the initialize data.
sub newsub new
{{
my $obj =my $obj = bless &construct,bless &construct, ''Foo::BounceFoo::Bounce';';
$initz{ refaddr $obj } = [ @_ ];$initz{ refaddr $obj } = [ @_ ];
$obj$obj
}}
Example: Multi-stage initialization
package Foo::Bounce;package Foo::Bounce;
AUTOLOADAUTOLOAD
{{
......
## reset the class, lazy initializereset the class, lazy initialize
bless $obj, 'Foo';bless $obj, 'Foo';
my $argzmy $argz = delete $initz{ refaddr $obj };= delete $initz{ refaddr $obj };
$obj->initialize( @$argz );$obj->initialize( @$argz );
goto &{ $object->can( $method ) }goto &{ $object->can( $method ) }
}}
Problem: Co-operating classes require classes.
Hard to add after the fact.
What if you can't re-write the class?
Wrapping every single class is not an option.
A generic solution is the only reasonable fix.
Another Pattern: “Flyweight Object”
Cheap to construct.
Stand-in in for a bulky, expensive object.
Example: Surrogate keys, Unix file descriptors.
Like references or pointers, managed by user code.
Flyweight: Trampoline Object
Replaces itself with another object.
Temporary stand-in for the “real” object.
Only behavior: creating a new object and re-dispatching.
Pass trough the class/object once.
How is it done?
Quite easily:
AUTOLOAD's can intercept method calls cleanly.
References modify the object in place.
goto &sub replaces a call on the stack.
Result: Method re-dispatched with a newly-minted object.
No if-logic, no re-construction, no key-magic.
Click to add code
The “Foo::Bar”
class...
my $obj =my $obj = Foo::BarFoo::Bar->foobar->foobar
((
qw( bim bam )qw( bim bam )
););
The “Foo::Bar”
class...
… becomes an
argument.
my $obj =my $obj = Foo::BarFoo::Bar->frob->frob
((
qw( bim bam )qw( bim bam )
););
my $obj =my $obj = Object::TrampolineObject::Trampoline->frob->frob
((
qw(qw( Foo::BarFoo::Bar bim bam )bim bam )
););
Click to add code
What you knead
Object::Trampoline is nothing but an AUTOLOAD.
Blesses a subref into Object::Trampoline::Bounce.
Yes, Virginia, Perl can bless something other than a hash.
Construct a bouncing object
package Object::Trampoline;package Object::Trampoline;
AUTOLOADAUTOLOAD
{{
shift;shift;
my ( $protomy ( $proto, @argz ) = @_;, @argz ) = @_;
my $methodmy $method = ( split /::/, $AUTOLOAD )[ -1 ];= ( split /::/, $AUTOLOAD )[ -1 ];
my $objmy $obj == sub { $proto->$method( @argz ) }sub { $proto->$method( @argz ) };;
bless $obj, 'bless $obj, 'Object::Trampoline::BounceObject::Trampoline::Bounce''
}}
Bouncing the object
Call the constructor: $_[0] = $_[0]->();
At this point it is no longer a Trampoline object.
AUTOLOAD deals with the method call.
Needs a stub DESTROY to avoid creating the object.
And, Viola!, the object you wanted.
AUTOLOADAUTOLOAD
{{
## assign $_[0] replaces caller's object.assign $_[0] replaces caller's object.
$_[0] = $_[0]->();$_[0] = $_[0]->();
my $class = blessed $_[0];my $class = blessed $_[0];
my $method = ( split /::/, $AUTOLOAD )[ -1 ];my $method = ( split /::/, $AUTOLOAD )[ -1 ];
local $a = $class->can( $method ) ) and goto &$a;local $a = $class->can( $method ) ) and goto &$a;
my $obj = shift;my $obj = shift;
$obj->$method( @_ )$obj->$method( @_ )
}}
DESTROY {}DESTROY {}
Override UNIVERSAL
All classes inherit DOES, VERSION, isa, can.
This means that the AUTOLOAD will not intercept them.
Fix: Overload UNIVERSAL methods to use AUTOLOAD.
Override UNIVERSAL
for my $name ( keys %{for my $name ( keys %{ $::{ 'UNIVERSAL::' }$::{ 'UNIVERSAL::' } } )} )
{{
*{ qualify_to_ref $name }*{ qualify_to_ref $name }
= sub= sub
{{
$AUTOLOAD = $name;$AUTOLOAD = $name;
goto &AUTOLOADgoto &AUTOLOAD
};};
}}
Extra-Bouncy: Object::Trampoline::Use
There are times when using the module is important.
Object::Trampoline::Use does a string eval.
Pushes a “use $package” into the caller's class.
Accommodates side-effects of import in the correct module.
Lazy “use”
AUTOLOADAUTOLOAD
{{
......
my $sub =my $sub =
subsub
{{
eval "package $caller; use $class” or croak ...or croak ...
$class->$method( @argz )$class->$method( @argz )
};};
bless $sub, 'bless $sub, 'Object::Trampoline::BounceObject::Trampoline::Bounce''
}}
Hence a subref:
Object::Trampoline::Bounce has no idea what the sub does.
Encapsulation gives a better division of labor:
One class knows what gets done, O::T::B does it reliably.
Any class that wants to can use O::T::B.
Feeding the Industrial Revolution
Shifted off the stack, lexical copies are replaced in-place.
Trivial to use Trampolines as factory classes.
Annoying if you don't plan correctly.
Shifting without factories
Data::Alias simplifies shifting objects off the stack:
alias my $obj = shift;
The “alias” leaves $obj as a reference to the original.
After that trampolines will do the right thing.
Example: Dealing with handles.
O::T originally developed for a call-center system.
DBI to query pending trouble tickets.
Asterisk to dial out to the group assigned to handle a ticket.
SIP to originate the calls.
Berkeley DB handle to query Asterisk status.
Testing complicated by unnecessary servers.
Fix was trampolines.
Sharing is caring
The server handles were installed from a common module.
Singleton handles update in place on use.
Testing only requires the handles actually exercised.
Sharing a handle
my %handlz =my %handlz =
((
dbhdbh => Object::Trampoline->new( @dbi_argz ),=> Object::Trampoline->new( @dbi_argz ),
sip_hsip_h => Object::Trampoline->new( @sip_argz ),=> Object::Trampoline->new( @sip_argz ),
......
););
sub importsub import
{{
my $callermy $caller = caller;= caller;
for my $name ( @_ )for my $name ( @_ )
{{
*{ qualify_to_ref $name, $caller }*{ qualify_to_ref $name, $caller } = $handlz{ $name };= $handlz{ $name };
}}
returnreturn
}}
Better Errors
Bad Error: “Error: File not found.”
Also Bad: “Error: xyz.config not found”
Fix: pre-check the files, reporting the working directory.
Add error messages with full paths, failure reasons.
Fix: codref blessed into O::T::Bounce.
Finding sanity
packagepackage Sane::FooSane::Foo
sub newsub new
{{
my $pathmy $path = shift;= shift;
bless subbless sub
{{
my $cwdmy $cwd = getcwd;= getcwd;
-e $path-e $path or die "Non-existant: '$path' ($cwd)";or die "Non-existant: '$path' ($cwd)";
-r _-r _ or die "Non-readable: '$path' ($cwd)";or die "Non-readable: '$path' ($cwd)";
......
Foo->new( $path )Foo->new( $path )
},},
'Object::Trampoline::Bounce''Object::Trampoline::Bounce'
}}
Automatic checking
Sane::Foo flyweight does the checking automatically.
Object::Trampoline::Bounce executes the object.
No outside data required: Just the closure.
Returns a Foo object after pre-check.
Trivial to add $$ check for crossing process boundarys.
Sufficiently developed technology...
Misguided magic:
Anyone who violates encapsulation gets what they deserve...
… or the objects have to be tied and overloaded.
Accidental factory objects.
Relying too early on import side-effects w/ O::T::Use.
References
On CPAN:
O::T is ~30 lines of code, 300 lines of POD & comments.
Alternative solutions:
Data::Thunk, Scalar::Defer, Data::Lazy.
Questions?

Object Trampoline: Why having not the object you want is what you need.

  • 1.
  • 2.
    We've all beenthere Starting the web server crashes your database. Testing requires working handles for services you don't test. Compiling error messages in 15 languages you never use.
  • 3.
    Patterns: Ways toScratch an Itch Language-specific, idiomatic solutions. Meta-data about language strengths, limitations. Example: C patterns are about pointers and string handling. Perl patterns here: objects, dispatch, and stack manglement.
  • 4.
    Pattern: “Lazy Initialization” Separate“construction” and “initialization”. You only need to construct an object to dispatch with it. Delay initialization until methods are called.
  • 5.
    Common way: If-logic Constructa naked object. Check object contents within every method, overload, ... Works until someone forgets to check... … or benchmarks the overhead of checking.
  • 6.
    Other ways: Use afactory class to construct a new object every time. Hard to maintain state across calls. Use a cached connection (e.g., DBI::connect_cached). Expensive: check what you know to be true every time. Not lazy or impatient.
  • 7.
    Pattern: Trampoline Class Simplestapproach: co-operating classes with re-bless. Construct the object in one class. Bless it into the “bounce class”. Bounce class initializes the object. Re-bless the object where it belongs.
  • 8.
  • 9.
    Example: Multi-stage initialization packageFoo;package Foo; ## new calls the constructor, re-blesses thenew calls the constructor, re-blesses the ## object then stores the initialize data.object then stores the initialize data. sub newsub new {{ my $obj =my $obj = bless &construct,bless &construct, ''Foo::BounceFoo::Bounce';'; $initz{ refaddr $obj } = [ @_ ];$initz{ refaddr $obj } = [ @_ ]; $obj$obj }}
  • 10.
    Example: Multi-stage initialization packageFoo::Bounce;package Foo::Bounce; AUTOLOADAUTOLOAD {{ ...... ## reset the class, lazy initializereset the class, lazy initialize bless $obj, 'Foo';bless $obj, 'Foo'; my $argzmy $argz = delete $initz{ refaddr $obj };= delete $initz{ refaddr $obj }; $obj->initialize( @$argz );$obj->initialize( @$argz ); goto &{ $object->can( $method ) }goto &{ $object->can( $method ) } }}
  • 11.
    Problem: Co-operating classesrequire classes. Hard to add after the fact. What if you can't re-write the class? Wrapping every single class is not an option. A generic solution is the only reasonable fix.
  • 12.
    Another Pattern: “FlyweightObject” Cheap to construct. Stand-in in for a bulky, expensive object. Example: Surrogate keys, Unix file descriptors. Like references or pointers, managed by user code.
  • 13.
    Flyweight: Trampoline Object Replacesitself with another object. Temporary stand-in for the “real” object. Only behavior: creating a new object and re-dispatching. Pass trough the class/object once.
  • 14.
    How is itdone? Quite easily: AUTOLOAD's can intercept method calls cleanly. References modify the object in place. goto &sub replaces a call on the stack. Result: Method re-dispatched with a newly-minted object. No if-logic, no re-construction, no key-magic.
  • 15.
    Click to addcode The “Foo::Bar” class... my $obj =my $obj = Foo::BarFoo::Bar->foobar->foobar (( qw( bim bam )qw( bim bam ) ););
  • 16.
    The “Foo::Bar” class... … becomesan argument. my $obj =my $obj = Foo::BarFoo::Bar->frob->frob (( qw( bim bam )qw( bim bam ) );); my $obj =my $obj = Object::TrampolineObject::Trampoline->frob->frob (( qw(qw( Foo::BarFoo::Bar bim bam )bim bam ) );); Click to add code
  • 17.
    What you knead Object::Trampolineis nothing but an AUTOLOAD. Blesses a subref into Object::Trampoline::Bounce. Yes, Virginia, Perl can bless something other than a hash.
  • 18.
    Construct a bouncingobject package Object::Trampoline;package Object::Trampoline; AUTOLOADAUTOLOAD {{ shift;shift; my ( $protomy ( $proto, @argz ) = @_;, @argz ) = @_; my $methodmy $method = ( split /::/, $AUTOLOAD )[ -1 ];= ( split /::/, $AUTOLOAD )[ -1 ]; my $objmy $obj == sub { $proto->$method( @argz ) }sub { $proto->$method( @argz ) };; bless $obj, 'bless $obj, 'Object::Trampoline::BounceObject::Trampoline::Bounce'' }}
  • 19.
    Bouncing the object Callthe constructor: $_[0] = $_[0]->(); At this point it is no longer a Trampoline object. AUTOLOAD deals with the method call. Needs a stub DESTROY to avoid creating the object.
  • 20.
    And, Viola!, theobject you wanted. AUTOLOADAUTOLOAD {{ ## assign $_[0] replaces caller's object.assign $_[0] replaces caller's object. $_[0] = $_[0]->();$_[0] = $_[0]->(); my $class = blessed $_[0];my $class = blessed $_[0]; my $method = ( split /::/, $AUTOLOAD )[ -1 ];my $method = ( split /::/, $AUTOLOAD )[ -1 ]; local $a = $class->can( $method ) ) and goto &$a;local $a = $class->can( $method ) ) and goto &$a; my $obj = shift;my $obj = shift; $obj->$method( @_ )$obj->$method( @_ ) }} DESTROY {}DESTROY {}
  • 21.
    Override UNIVERSAL All classesinherit DOES, VERSION, isa, can. This means that the AUTOLOAD will not intercept them. Fix: Overload UNIVERSAL methods to use AUTOLOAD.
  • 22.
    Override UNIVERSAL for my$name ( keys %{for my $name ( keys %{ $::{ 'UNIVERSAL::' }$::{ 'UNIVERSAL::' } } )} ) {{ *{ qualify_to_ref $name }*{ qualify_to_ref $name } = sub= sub {{ $AUTOLOAD = $name;$AUTOLOAD = $name; goto &AUTOLOADgoto &AUTOLOAD };}; }}
  • 23.
    Extra-Bouncy: Object::Trampoline::Use There aretimes when using the module is important. Object::Trampoline::Use does a string eval. Pushes a “use $package” into the caller's class. Accommodates side-effects of import in the correct module.
  • 24.
    Lazy “use” AUTOLOADAUTOLOAD {{ ...... my $sub=my $sub = subsub {{ eval "package $caller; use $class” or croak ...or croak ... $class->$method( @argz )$class->$method( @argz ) };}; bless $sub, 'bless $sub, 'Object::Trampoline::BounceObject::Trampoline::Bounce'' }}
  • 25.
    Hence a subref: Object::Trampoline::Bouncehas no idea what the sub does. Encapsulation gives a better division of labor: One class knows what gets done, O::T::B does it reliably. Any class that wants to can use O::T::B.
  • 26.
    Feeding the IndustrialRevolution Shifted off the stack, lexical copies are replaced in-place. Trivial to use Trampolines as factory classes. Annoying if you don't plan correctly.
  • 27.
    Shifting without factories Data::Aliassimplifies shifting objects off the stack: alias my $obj = shift; The “alias” leaves $obj as a reference to the original. After that trampolines will do the right thing.
  • 28.
    Example: Dealing withhandles. O::T originally developed for a call-center system. DBI to query pending trouble tickets. Asterisk to dial out to the group assigned to handle a ticket. SIP to originate the calls. Berkeley DB handle to query Asterisk status. Testing complicated by unnecessary servers. Fix was trampolines.
  • 29.
    Sharing is caring Theserver handles were installed from a common module. Singleton handles update in place on use. Testing only requires the handles actually exercised.
  • 30.
    Sharing a handle my%handlz =my %handlz = (( dbhdbh => Object::Trampoline->new( @dbi_argz ),=> Object::Trampoline->new( @dbi_argz ), sip_hsip_h => Object::Trampoline->new( @sip_argz ),=> Object::Trampoline->new( @sip_argz ), ...... );); sub importsub import {{ my $callermy $caller = caller;= caller; for my $name ( @_ )for my $name ( @_ ) {{ *{ qualify_to_ref $name, $caller }*{ qualify_to_ref $name, $caller } = $handlz{ $name };= $handlz{ $name }; }} returnreturn }}
  • 31.
    Better Errors Bad Error:“Error: File not found.” Also Bad: “Error: xyz.config not found” Fix: pre-check the files, reporting the working directory. Add error messages with full paths, failure reasons. Fix: codref blessed into O::T::Bounce.
  • 32.
    Finding sanity packagepackage Sane::FooSane::Foo subnewsub new {{ my $pathmy $path = shift;= shift; bless subbless sub {{ my $cwdmy $cwd = getcwd;= getcwd; -e $path-e $path or die "Non-existant: '$path' ($cwd)";or die "Non-existant: '$path' ($cwd)"; -r _-r _ or die "Non-readable: '$path' ($cwd)";or die "Non-readable: '$path' ($cwd)"; ...... Foo->new( $path )Foo->new( $path ) },}, 'Object::Trampoline::Bounce''Object::Trampoline::Bounce' }}
  • 33.
    Automatic checking Sane::Foo flyweightdoes the checking automatically. Object::Trampoline::Bounce executes the object. No outside data required: Just the closure. Returns a Foo object after pre-check. Trivial to add $$ check for crossing process boundarys.
  • 34.
    Sufficiently developed technology... Misguidedmagic: Anyone who violates encapsulation gets what they deserve... … or the objects have to be tied and overloaded. Accidental factory objects. Relying too early on import side-effects w/ O::T::Use.
  • 35.
    References On CPAN: O::T is~30 lines of code, 300 lines of POD & comments. Alternative solutions: Data::Thunk, Scalar::Defer, Data::Lazy. Questions?