重新提出问题 - 抱歉,这有点长。
有一个简单的包,例如
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”My
和My::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
这只有Moo
,我不能使用更小的{{1}},对吗?
Ps:对于极长的问题再次抱歉。
答案 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。cmd1
和cmd2
,除非您有理由将它们分开并按需加载它们。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($_);
}