在perl中按需加载所需的包

时间:2014-08-20 09:35:43

标签: perl moose

重新提出问题 - 抱歉,这有点长。

有一个简单的包,例如

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

我希望允许其他人使用其他方法扩展My,例如

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

这允许使用“base”MyMy::Cmd3中的方法与下一个:

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

不是我想要的。我不想use My::Cmd3;,(这里会有更多扩展程序包),我想要use My;

使用角色是NICER,例如:

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;

这允许我:

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

但是当有人制作My::Cmd4时,需要更改基础My包以添加with My::Cmd4。 ;(

我正在寻找一种方法,如何实现下一步:

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

所以,我现在拥有的东西:

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;

设计正确的对象层次结构是我永恒的问题..;(

我绝对肯定,这不应该是一个大问题,只需要一些我应该学习的指针;因此很高兴知道一些CPAN模块,使用这种技术......

所以问题:

  • 我需要代替上述“什么在这里?”
  • “扩展”包应该是角色吗? (可能这是最好的,但要求肯定)
  • 我应该将“基本”命令从My移动到例如My::Base My::Something并按其他My加载点播,还是应该保留在my $obj = My->new(....); my @methods = $obj->meta->get_all_methods(); ?为什么?
  • 其他一些建议?
  • 为了获得方法列表(和加载的包),在Moose中我可以使用
Moose

这只有Moo,我不能使用更小的{{1}},对吗?

Ps:对于极长的问题再次抱歉。

4 个答案:

答案 0 :(得分:3)

以下是填充WHAT HERE?部分的解决方案,其中扩展名仍为角色。

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;
}
...

注意:

  • 您需要在运行时加载角色。这与require My::Role类似,但该模块处理运行时加载模块的一些问题。这里我使用了Class::Load,但是存在许多替代方案,包括Module :: Load。
  • 然后您需要将角色应用于您的对象(另请参阅this Moose Cookbook entry作为参考)。
  • 我建议在这个基类中保留方法cmd1cmd2,除非您有理由将它们分开并按需加载它们。
  • 我使用BUILD方法,在构建后自动调用Moose。
  • 我不允许commands成为undef所以我不需要检查它 - 如果没有命令,那么它可以保留为空的arrayref

您还可以使用一个模块,为您提供应用角色的基础架构,而无需您自己编写。在这里,我使用了MooseX::Traits,但此处列出了许多替代方案: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

然后使用该课程:

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;

如果你不想使用Moose,仍然可以用Moo做这样的事情:

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;

答案 1 :(得分:3)

如上所述,"我的"本身应该作为一个角色来实现。除了少数例外,类代表名词。如果您的班级的消费者真正需要在没有子类化的情况下添加行为,那么您的课程可能还没有完成。例如:

package Animal;
use Moose;

sub eat { ... }
sub excrete { ... }

如果您的代码的消费者需要"生育"方法,然后他们应该修改Animal类本身,而不是创建另一个动态加载模块。如果你不想让他们修改Animal,那么他们要做的就是将它子类化为一个新的类,比如,#34; FruitfulAnimal"。

如果您的消费者想要"吃"和"排泄"当他们碰巧实现一个Animal类时,他们最好使用一个提供这些行为的角色。

以下是我作为角色的实现:

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;

如何消耗角色

package AnyClassThatConsumesMy;

use Moose;

# Instead of My->new( commands => [qw(Cmd3 Cmd4)] );
with 'My', 'Cmd3', 'Cmd4';

1;

测试

#!/usr/bin/perl

use Modern::Perl;
use AnyClassThatConsumesMy;

my $my = AnyClassThatConsumesMy->new();

say $my->cmd1();
say $my->cmd2();
say $my->cmd3();
say $my->cmd4();

输出

1
2
3
4

我建议这种方法的原因是你的例子非常关注行为而不是建模特定的东西。您希望从一系列行为开始,让其他人做出新的行为。这可能看似违反直觉,因为在OO设计文本中通常不会强调角色。这是因为许多OO语言都没有对类似角色的行为提供强大的支持。它不必实例化 - 能够使用。

答案 2 :(得分:2)

首先:继承

使用Moo或Moose是一项非常简单的任务:

package My::Sub3;
use Moo;
extends 'My';

sub cmd3 {3}
1;

第二:动态对象构建。定义构建函数并在运行时加载适当的模块。有几种方法可以做到这一点,我喜欢Module::Load CPAN模块:

use Module::Load;

sub my_factory_builder {
   my $class_name = shift;
   load $class_name;
   return $class_name->new(@_);
}

然后,在你的代码中:

my @new_params = ();
my $object = my_factory_builder('My::Sub3', @new_params);

答案 3 :(得分:1)

我可以从问题中看出,你需要使用方法继承人

一般包

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;

我:: CMD3

package My::Cmd3;

sub cmd3 {ref shift};
1;

我:: CMD4

package My::Cmd4;

sub cmd4 {ref shift};
sub isCMd4 {print "it is cmd4"};
1;

测试

#!/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;

输出

1
2
My
My
it is cmd41

获取方法列表

for(keys %My::) {
    say $_ if My->can($_);
}