-
Notifications
You must be signed in to change notification settings - Fork 2
hoytech/Object-Sub
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
package Object::Sub; use strict; our $VERSION = '0.103'; our $AUTOLOAD; use overload fallback => 1, '&{}' => sub { my $self_ref = \$_[0]; return sub { return $$self_ref->{__object_sub_internal_cb}->($$self_ref, undef, @_); }; }; sub AUTOLOAD { die "$_[0] is not an object" if !ref $_[0]; my $name = $AUTOLOAD; $name =~ s/.*://; return $_[0]->{__object_sub_internal_cb}->($_[0], $name, @_[1 .. $#_]); } sub new { my ($class, $cb) = @_; if (ref $cb eq 'HASH') { my $orig_cb = $cb; $cb = sub { ($orig_cb->{$_[1]} || die "unable to find method $_[1]")->($_[0], @_[2 .. $#_]); }; } die "need a callback" if ref $cb ne 'CODE'; my $self = { __object_sub_internal_cb => $cb, }; bless $self, $class; return $self; } ## Prevent DESTROY method from being handled by AUTOLOAD sub DESTROY { } 1; __END__ =encoding utf-8 =head1 NAME Object::Sub - Create objects without those pesky classes =head1 SYNOPSIS use Object::Sub; my $obj = Object::Sub->new(sub { my ($self, $method, @args) = @_; print "self: $self, method name: $method, first arg: $args[0]\n"; }); $obj->whatever(123); ## self: Object::Sub=HASH(0xc78eb0), method name: whatever, first arg: 123 $obj->(123); ## self: Object::Sub=HASH(0xc78eb0), method name: , first arg: 123 ## ($method is undef) Alternatively, you can use a hash of subs: my $obj = Object::Sub->new({ add => sub { my ($self, $num1, $num2) = @_; return $num1 + $num2; }, mul => sub { my ($self, $num1, $num2) = @_; return $num1 * $num2; }, }); $obj->add(2, 3); ## => 5 =head1 DESCRIPTION Sometimes you want something that acts like an object but you don't want to go to all the trouble of creating a new package, with constructor and methods and so on. This module is a trivial wrapper around perl's L<AUTOLOAD> functionality which intercepts method calls and lets you handle them in a single C<sub>. It also uses L<overload> so that you can additionally treat the object as a C<sub> if you desire. =head1 USE-CASES =head2 AUTOLOAD SYNTACTIC SUGAR L<AUTOLOAD> allows you to dispatch on method names at run-time which can sometimes be useful, for example in RPC protocols where you transmit method call messages to another process for them to be executed remotely. Unfortunately, using L<AUTOLOAD> is a bit annoying since the interface is somewhat arcane. L<Object::Sub> is a nicer interface to the most commonly used AUTOLOAD functionality: my $obj = Object::Sub->new(sub { my ($self, $method, @args) = @_; my $rpc_input = encode_json({ method => $method, args => [ @args ] }); my $rpc_output = do_rpc_call($rpc_input); return decode_json($rpc_output); }); Because C<Object::Sub> objects can also be treated as subs, your RPC interface can support sub-routine calls on the objects as well as method calls, even on the same object. =head2 PLACE-HOLDER OBJECTS Some APIs require you to pass in or provide an object but then don't actually end up using it. Instead of passing in undef and getting a weird C<Can't call method "XYZ" on an undefined value> error, you can pass in an L<Object::Sub> which will throw a "helpful" exception instead: my $obj = Some::API->new( logger => Object::Sub->new(sub { die "FIXME: add logger" }), ); Alternatively, you may choose to minimally implement the API "inline" in your program: my $obj = Some::API->new( logger => Object::Sub->new(sub { my ($self, $method, @args) = @_; return if $method eq 'debug'; say STDERR "Some::API $method: " . join(' ', @args); }) ); =head2 LAZY OBJECT CREATION Again, some APIs may never end up using an object so you may wish to "lazily" defer the creation of that object until a method is actually called on it. This module can help you make the cases where it doesn't use it more efficient. For example, suppose you have a large L<CGI> script which always opens a L<DBI> connection but only actually accesses this connection for a small portion of runs. You can prevent the script from accessing the database on the majority of runs with L<Object::Sub>: my $dbh = Object::Sub->new(sub { require DBI; $_[0] = DBI->connect($dsn, $user, $pass, { RaiseError => 1 }) || die "Unable to connect to database: $DBI::errstr"; my ($self, $method, @args) = @_; return $self->$method(@args); }); Note how we don't even load or compile the module until the first method is called. After you call a method on C<$dbh> it changes from a C<Object::Sub> object into a C<DBI> object (assuming the C<< DBI->connect >> constructor succeeds). This works because the C<$_[0]> argument is actually an alias to C<$dbh> and can be modified. To demonstrate this, here is an example with L<Session::Token>: my $o = Object::Sub->new(sub { require Session::Token; $_[0] = Session::Token->new; my ($self, $method, @args) = @_; return $self->$method(@args); }); say ref $o; ## Object::Sub say $o->get; ## mhDPtfLlFMGl5kyNcJgFt7 say ref $o; ## Session::Token say $o->get; ## 4JYkGgwWbYWGleU7Qk912P With L<Object::Sub> you can lazily "create" and pass around objects before their constructor code has even been loaded. =head1 BUGS Although not really a bug in this module, common perl code tends to copy references of objects. Any code that overwrites the caller object (for example in the L<LAZY OBJECT CREATION> section) will only update one of the copies. =head1 SEE ALSO L<Object-Sub github repo|https://github.com/hoytech/Object-Sub> =head1 AUTHOR Doug Hoyte, C<< <doug@hcsw.org> >> =head1 COPYRIGHT & LICENSE Copyright 2015-2016 Doug Hoyte. This module is licensed under the same terms as perl itself.
About
Create objects without those pesky classes
Resources
Stars
Watchers
Forks
Packages 0
No packages published