perl oo动态方法

时间:2013-06-06 10:25:30

标签: perl object dynamic methods

我想动态地在类中定义方法。我正在写一个跟踪器,比下面的骨架更复杂,它也有状态意识,但这与我的问题无关。 我用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()

6 个答案:

答案 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中,但这会使事情变得更加复杂。

我想它现在尽可能地做我想要的。