可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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;
回答2:
As described, "My" itself should be implemented as a role. With few exceptions, classes represent nouns. If the consumers of your class genuinely need to add behavior without subclassing, then your class probably isn't finished yet. For example:
package Animal;
use Moose;
sub eat { ... }
sub excrete { ... }
If the consumers of your code need a "procreate" method, then they should modify the Animal class itself rather than create another module for dynamic loading. If you don't want them modifying Animal, then the right thing for them to do is to subclass it in a new class, say, "FruitfulAnimal".
If your consumers want "eat" and "excrete" behaviors when they happen to be implementing an Animal class, it would be better for them to consume a role that provides those behaviors.
Here is an implementation of My as a Role:
package My;
use Moose::Role;
sub cmd1 { 1 }
sub cmd2 { 2 }
1;
Cmd3
package Cmd3;
use Moose::Role;
with 'My'; # consumes the behaviors of the 'My' Role
sub cmd3 { 3 }
1;
Cmd4
package Cmd4;
use Moose::Role;
with 'My'; # consumes the behaviors of the 'My' Role
sub cmd4 { 4 }
1;
How the role is consumed
package AnyClassThatConsumesMy;
use Moose;
# Instead of My->new( commands => [qw(Cmd3 Cmd4)] );
with 'My', 'Cmd3', 'Cmd4';
1;
test
#!/usr/bin/perl
use Modern::Perl;
use AnyClassThatConsumesMy;
my $my = AnyClassThatConsumesMy->new();
say $my->cmd1();
say $my->cmd2();
say $my->cmd3();
say $my->cmd4();
Output
1
2
3
4
The reason I suggest this approach is that your example is deeply concerned with behaviors rather than modeling something specific. You want to start with a set of behaviors and have others contribute new behaviors. This may seem counter-intuitive because Roles aren't typically emphasized in OO design texts. This is because so many OO languages don't have robust support for Role-like behavior. It doesn't have to be instantiate-able to be usable.
回答3:
First: Inheritance
Using Moo or Moose is a super-easy task:
package My::Sub3;
use Moo;
extends 'My';
sub cmd3 {3}
1;
Second: Dynamic object build. Define a build function and load at runtime the proper module. There are several ways to do this, I like the Module::Load CPAN module:
use Module::Load;
sub my_factory_builder {
my $class_name = shift;
load $class_name;
return $class_name->new(@_);
}
And then, in your code:
my @new_params = ();
my $object = my_factory_builder('My::Sub3', @new_params);
回答4:
I could tell from the question, you need to use methods heirs
General package
package My;
sub new ($$) {
my $caller = shift;
my $commands = shift;
# blank
my $self = {};
# commands for implements
foreach my $cmd (@{$commands}) {
# implement support extend commands
require "My/".ucfirst($cmd).".pm";
push @ISA, "My::".ucfirst($cmd);
}
return bless $self, $caller;;
}
sub cmd1 {1};
sub cmd2 {2};
1;
My::Cmd3
package My::Cmd3;
sub cmd3 {ref shift};
1;
My::Cmd4
package My::Cmd4;
sub cmd4 {ref shift};
sub isCMd4 {print "it is cmd4"};
1;
test
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use v5.10;
use My;
my $my = My->new([qw(cmd3 cmd4)]);
say $my->cmd1();
say $my->cmd2();
say $my->cmd3();
say $my->cmd4();
say $my->isCMd4();
1;
Output
1
2
My
My
it is cmd41
Get a list of methods
for(keys %My::) {
say $_ if My->can($_);
}