Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading the needed packages on demand in perl

Tags:

perl

moose

Reworded question - sorry, it is a bit long.

Have a simplyfied package for example

package My;
use Moose;
use namespace::sweep;
sub cmd1 {1}
sub smd2 {2}
__PACKAGE__->meta->make_immutable;
1;

I want allow to others extending the My with another methods, such

package My::Cmd3;
use Moose;
extends 'My';
sub cmd3 {3}
1;

This allows to use the methods from the "base" My and My::Cmd3 with the next:

use My::Cmd3;
my $obj = My::Cmd3->new();
say $obj->cmd1(); #from the base My
say $obj->cmd3(); #from the My::Cmd3;

But this isn't what I want. I don't want use My::Cmd3;, (here will be more extension packages), I want use My;.

Using roles is NICER, like:

package My;
use Moose;
with 'My::Cmd3';
sub cmd1 {1}
sub cmd2 {2}
__PACKAGE__->meta->make_immutable;
1;

package My::Cmd3;
use Moose::Role;
use namespace::autoclean;       
sub cmd3 {3};
no Moose::Role;
1;

This allows me:

use My;
my $obj = My->new();
say $obj->cmd1();
say $obj->cmd3(); #from the role-package

But when someone make an My::Cmd4 will need change the base My package to add with My::Cmd4. ;(

I'm looking for a way, how to achieve the next:

use My;

#and load all needed packages on demand with the interface like the next
my $obj = My->new( commands => [qw(Cmd3 Cmd4)] );
#what should load the methods from the "base" My and from the wanted extensions too

say $obj->cmd1(); # from the base My package
say $obj->cmd3(); # from the "extension" package My::Cmd3
say $obj->cmd4(); # from the My::Cmd4

So, the what I have now:

package My;
use Moose;
has 'commands' => (
    is => 'rw',
    isa => 'ArrayRef[Str]|Undef', #??
    default => sub { undef },
);

# WHAT HERE?
# need something here, what loads the packages My::Names... based on the supplied "commands"
# probably the BUILD { ... } ???

sub cmd1 {1}
sub smd2 {2}
__PACKAGE__->meta->make_immutable;
1;

Designing an right object hierarchy is my everlasting problem.. ;(

I'm absolutely sure than this isn't should be an big problem, only need some pointers what I should study; and therefore Would be nice to know some CPAN modules, what using such technique ...

So the questions:

  • What I need to put in place of the above "WHAT HERE?"
  • The "extension" packages should be roles? (probably it is the best for this, but asking for sure)
  • Should i move the "base" commands from the My to the e.g. My::Base and load the on-demand as other My::Something or should they remain in the My? And why?
  • Some other recommendations?
  • To allow get a list of methods (and loaded packages), in Moose I can use
my $obj = My->new(....);
my @methods = $obj->meta->get_all_methods();

This has only Moose and I couldn't use something smaller as Moo, right?

Ps: Sorry again for the extremelly long question.

like image 375
jm666 Avatar asked Aug 20 '14 09:08

jm666


1 Answers

Here is a solution that fills in your WHAT HERE? section, with the extensions remaining as roles.

package My;

use Moose;
use Class::Load 'load_class';

has commands => (
    is      => 'ro',
    isa     => 'ArrayRef',
    default => sub { [ ] },
);

sub BUILD {
    my ($self) = @_;

    my $namespace = __PACKAGE__;
    foreach ( @{ $self->commands } ) {
        my $role = "$namespace::$_";
        load_class $role;           # load the module
        $role->meta->apply($self);  # apply the role to the object
    }
    return;
}
...

Notes:

  • You will need to load your role during runtime. This is akin to require My::Role but the module deals with some issues with loading modules at runtime. Here I have used Class::Load, but a number of alternatives exist including Module::Load.
  • Then you need to apply the role to your object (see also this Moose Cookbook entry as a reference).
  • I recommend keeping methods cmd1 and cmd2 in this base class unless you have a reason for separating them out and loading them on demand also.
  • I use the BUILD method which in Moose is invoked automatically after construction.
  • I don't allow commands to be undef so I don't need to check for it - If there are no commands, then it can be left as an empty arrayref.

You could also use a module that gives you the infrastructure for applying the roles without you having to write it yourself. Here I have used MooseX::Traits, but again there are a number of alternatives listed here: https://metacpan.org/pod/Task::Moose#Traits-Roles

package My;

use Moose;

with 'MooseX::Traits';
has '+_trait_namespace' => ( default => 'My' );

sub cmd1 {1}
sub cmd2 {2}
__PACKAGE__->meta->make_immutable;
1;

# your roles remain unchanged

Then to use the class:

use My;
my $obj = My->with_traits(qw[ Cmd3 Cmd4 ])->new;
# or my $obj = My->new_with_traits( traits => [qw( Cmd3 Cmd4 )] );

say $obj->cmd1;
say $obj->cmd3;
say $obj->cmd4;

It is still possible to do something like this with Moo if you don't want to use Moose:

use Moo::Role ();
my $class = Moo::Role->create_class_with_roles( 'My2', 'My::Cmd3', 'My::Cmd4' );
my $obj = $class->new;
say $obj->cmd1;
say $obj->cmd3;
say $obj->cmd4;
like image 72
stevenl Avatar answered Sep 22 '22 19:09

stevenl