我想动态地在类中定义方法。我正在写一个跟踪器,比下面的骨架更复杂,它也有状态意识,但这与我的问题无关。 我用trace方法编写了一个TraceSlave类,调用sprintf,用text \ n替换换行符,一切都很好。
基本上我想将我的跟踪实例化为:
my @classes = qw(debug token line src match);
my $trace = Tracer->new(\@classes);
我应该能够将动态定义的跟踪方法称为:
$trace->debug("hello, world");
$trace->match("matched (%s)(%s)(%s)(%s)(%s)", $1, $2, $3, $4, $5);
所以我的Tracer课程看起来像:
package Tracer;
sub new {
my $class = shift;
my $self = {};
my @traceClasses = @{$_[0]};
bless $self, $class;
for (@traceClasses) {
# This next line is wrong, and the core of my question
$self->$_ = new TraceSlave($_, ...)->trace
} # for (@traceClasses)
}
好吧它没有,因为那不编译。基本上我想定义Tracer实例的方法,作为TraceSlave实例的trace方法;在循环中。
我可以用AUTOLOAD或eval来做,但那是错的。什么是正确的方式?
这是TraceSlave的完整性。没关系
package TraceSlave;
sub new {
my $self = { header => $_[1], states => $_[2], stateRef => $_[3] };
bless $self, $_[0];
return $self;
} # new()
sub trace {
my $self = shift;
my @states = @{$self->{states}};
if ($states[${$self->{stateRef}}]) { # if trace enabled for this class and state
my @args;
for (1..$#_) { ($args[$_-1] = $_[$_]) =~ s/\n/\\n/g; } # Build args for sprintf, and replace \n in args
print $self->{header}.sprintf($_[0], @args)."\n";
}
} # trace()
答案 0 :(得分:2)
每当我开始进入运行时修改类时,我就开始使用MOP和Moose。所以,如果我正确地读到这个,你想要的是
package Tracer;
use strict;
use warnings;
use Moose;
use TraceSlave;
has 'classes' => ( is => 'ro', isa => 'ArrayRef[Str]', required => 1 );
### This is to allow ->new(\@classes) invocation instead of just
### using ->new( classes => \@classes) for invocation
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if ( @_ == 1 && ref $_[0] eq 'ARRAY' ) {
return $class->$orig( classes => $_[0] );
}
else {
return $class->$orig(@_);
}
};
sub BUILD {
my $self = shift;
for my $class (@{$self->classes}) {
my $tracer = TraceSlave->new($class, ...);
$self->meta->add_method( $class => sub { $tracer->trace(@_) } );
}
}
虽然我很确定这只是做同样的事情并最终成为一个字符串评估。我根本没有挖到MOP内部。 (我也不是100%肯定这是正确的代码或与Moose做事的最佳方式,所以买家要小心。:))
答案 1 :(得分:0)
我通常使用字符串eval来定义子例程:
for my $method (@classes) {
eval "sub $method { 'TraceSlave'->new('$method', ...)->trace }";
}
答案 2 :(得分:0)
嗯,我已经完成了我的小模块,并且学到了很多关于参考资料的知识。我正在动态定义方法,但使用我认为非常脏的eval。然而,没有人似乎有更好的主意,所以在这里。
创建动态方法的行就在评论之后:#非常脏的动态方法创建,肯定有更好的方法吗?
所以我仍然有兴趣听听更好的方法。正如有人说我可以使用Moose,但无论如何,Moose会为我做一个评估。
#!/usr/bin/perl
use strict;
use warnings;
package TraceSlave;
sub new {
my $self = { header => $_[1], states => $_[2], stateRef => $_[3] };
bless $self, $_[0];
return $self;
} # new()
sub trace {
my $self = shift;
if ($self->{states}->[${$self->{stateRef}}]) { # if trace enabled for this class and state
my @args;
for (1..$#_) { ($args[$_-1] = $_[$_]) =~ s/\n/\\n/g; } # Build args for sprintf, and replace \n in args
print $self->{header}.sprintf($_[0], @args)."\n";
}
} # trace()
package Tracer;
sub new {
my ($class, $classList, $stateCount, $stateRef) = @_;
my $self = { states => {}, slaves => [], count => $stateCount };
my @classes = split(/\s+/, $classList);
my $maxlen = 0;
for (@classes) { # loop over all trace classes to find longest
my $len = length($_);
$maxlen = $len if $len > $maxlen;
}
$maxlen++; # Add a space
for (0..$#classes) { # loop over all trace classes, and eval create a slave for each class
my $tc = $classes[$_];
my $states = $self->{states}->{$tc} = [];
for (0..$stateCount) { $states->[$_] = 0; }
$self->{slaves}[$_] = TraceSlave->new( "$tc:"." "x($maxlen-length($tc)), $states, $stateRef );
# Very dirty creation of dynamic method, surely there's a better way?
eval("sub $tc { ".'$self=shift; $self->{slaves}['.$_.']->trace(@_); }');
} # for (0..$#classes)
bless $self, $class;
return $self;
} # new()
sub _onOff { # switch on traces
my ($self, $onOff, $classList, $statesRef) = @_;
my @classes = split(/\s+/, $classList);
my $count = $self->{count};
for (@classes) { # loop over all supplied trace classes and switch on/off for state list
my $states = $self->{states}->{$_};
if ($statesRef) { for (@$statesRef) { $states->[$_] = $onOff; } }
else { for (0..$count) { $states->[$_] = 1; } } # switch on for all states is no state list
} # for (0..$#classes)
} # on()
sub on {
my $self = shift;
$self->_onOff( 1, @_ );
}
sub off {
my $self = shift;
$self->_onOff( 0, @_ );
}
1;
答案 3 :(得分:0)
如果修改包的符号表以向该类添加方法,则不能有两个具有相同命名方法的不同语义的实例。在这种情况下使用AUTOLOAD似乎对我来说非常好。
问候,马蒂亚斯
答案 4 :(得分:0)
忽略“跟踪器”问题的细节,您只是希望能够为给定的包动态创建方法是否正确?那怎么样?
sub new {
my ($class, $trace_classes) = @_;
# ...
foreach my $tc (@$trace_classes) {
no strict 'refs';
*{"${class}::${tc}"} = sub {
my $self = shift;
# ...
};
}
return $self;
}
虽然在new
中执行此操作似乎非常非常奇怪!所以也许我错过了这一点
答案 5 :(得分:0)
@Unk回答上面Unk的小代码片段。
foreach my $tc (@classes) { # loop over all trace classes, and create a slave for each class
my $states = $self->{states}->{$tc} = [];
$slave = TraceSlave->new( "$tc:"." "x($maxlen-length($tc)), $states, $stateRef );
no strict 'refs';
*{"${class}::$tc"} = sub { $slave->trace(@_[1..$#_]); }
} # foreach my $tc (@classes)
bless $self, $class;
return $self;
} # new()
这当然要清洁得多。我不需要在Tracer $ self中拥有奴隶了。
但是我应该能够用:
替换整个子*{"${class}::${tc}"} = $slave->trace;
当然无效,因为我需要引用$ slave-> trace,上面的代码只会调用它。可悲的是,我也不明白使用glob,或者左边的很多参考资料。在我的辩护中,我可以用C中的指针或javascript中的引用做任何事情,但perl引用是相当一顿饭。还在学习。
我认为OO是正确的方法,因为跟踪器有很多私有数据,特别是哪些跟踪类用于哪种状态,当然还有每个跟踪类的标头。如果我可以让上面的简单赋值工作,那么嵌套对象也是正确的。
所有这些都是我为VHDL项目编写make系统的一部分。我强迫自己在perl中这样做,因为我认为是时候我正确地学习语言而不是写10行胶带。
我现在意识到* {“$ {class} :: $ {tc}”} = \& trace->奴隶可能永远不会工作。第一个参数是Tracer实例还是TraceSlave实例?要工作,它必须是TraceSlave实例,但这是Tracer类中定义的方法。当然,我可以把奴隶放回Tracer $ self中,但这会使事情变得更加复杂。
我想它现在尽可能地做我想要的。