前言注意:为了便于讨论,请暂时忽略这样一个事实:使用
Class::Accessor可以实现同一目的,或者甚至只使用
Moose(在考虑代码可读性和可维护性时可能会有更好的结果) .
关于面向对象的Perl,Programming Perl一书讨论了使用闭包生成存取方法的能力.例如,这是一段有效的代码:
#!perl use v5.12; use warnings; # at run-time package Person1; my @attributes = qw/name age address/; for my $att ( @attributes ) { my $accessor = __PACKAGE__ . "::$att"; no strict 'refs'; # allow symbolic refs to typeglob *$accessor = sub { my $self = shift; $self->{$att} = shift if @_; return $self->{$att}; }; } sub new { bless {},shift } package main; use Data::Dumper; my $dude = Person1->new; $dude->name('Lebowski'); say Dumper($dude);
在上面的例子中,如果我没有弄错的话,该类是在运行时组成的,其访问器是在实例化类的同时创建的.这意味着对象创建将受到速度惩罚.
现在考虑以下替代方案:
#!perl use v5.12; use warnings; package Person2; BEGIN { for my $att (qw/name age address/) { my $accessor = __PACKAGE__ . "::$att"; no strict 'refs'; # allow symbolic refs to typeglob *$accessor = sub { my $self = shift; $self->{$att} = shift if @_; return $self->{$att}; }; } } sub new { bless {},shift } package main; use Data::Dumper; my $dude = Person2->new; $dude->name('Lebowski'); say Dumper($dude);
在这个版本中,组合是在一个BEGIN块内(即在编译时),我相信通过在程序的生命周期中尽快处理这个任务,我在对象实例化期间节省了时间在运行时.
一个简单的基准,
# benchmark it! package main; use Benchmark qw/cmpthese/; cmpthese(-2,{ accessors_new => sub { Person1->new },accessors_begin => sub { Person2->new },});
这些结果似乎支持了我的理论:
Rate accessors_begin accessors_new accessors_begin 853234/s -- -9% accessors_new 937924/s 10% --
假设到目前为止我的推理是正确的,
>比较这两种策略时还有哪些其他好处/缺点?
>依靠BEGIN块作为进行这种类操作的有效方法是一个好主意吗?
>什么时候不推荐?
解决方法
当我运行你的基准测试时,我会得到相当多的颤动,这可以解释你的差异.对于10%或更小的差异,运行几次以确定.
Rate accessors_begin accessors_new accessors_begin 1865476/s -- -4% accessors_new 1943339/s 4% -- Rate accessors_begin accessors_new accessors_begin 1978799/s -- -1% accessors_new 2001062/s 1% -- Rate accessors_new accessors_begin accessors_new 1943339/s -- -2% accessors_begin 1988089/s 2% -- Rate accessors_begin accessors_new accessors_begin 1796509/s -- -8% accessors_new 1949296/s 9% -- Rate accessors_begin accessors_new accessors_begin 1916122/s -- -3% accessors_new 1969595/s 3% --
但实际上你所有的基准测试都是新的{bless {},shift}.对自己进行基准测试同样会强调颤抖.生成访问器的工作已经在加载代码时完成,并且从未进入,BEGIN是否阻止.
Perl没有单个编译时和运行时.相反,使用,需要或逐渐使用的每个东西都经历了它自己的编译和运行时步骤.使用Some :: Class会导致Some / Class.pm执行编译和运行时执行BEGIN,编译子例程然后执行任何其他代码.代码是在模块内的BEGIN块内部还是外部,对该模块外部的代码没有什么区别.