Perl:在方法中调用时,ref($ self)是否可以返回__PACKAGE__或undef以外的任何内容?

时间:2009-10-30 11:03:04

标签: perl oop

我在类中有方法,我需要确保只在对象实例上调用,而不是作为类方法。

我可能会做这样的事情:

# Edit: this is terrible, don't do this, it breaks inheritance.
sub foo
{
  my ($self) = @_;

  if (ref($self) ne __PACKAGE__) { return; }

  ...do stuff
}

但我认为这样做会更有效率:

sub foo
{
  my ($self) = @_;

  if (not ref($self)) { return; }

  ...do stuff
}

问题:

  1. 是否可以安全地假设如果ref()返回不是undef它将返回当前包?

  2. 我希望在我的所有健全性检查方法中回过头来做这样的事情。这是个坏主意吗?

  3. 是否有更多的方式来做我想做的事情?

  4. 在这种情况下,“使用驼鹿”不是可接受的答案。但是,如果您不得不这样说,请告诉我moose如何使这简单或更有效。我可能想将它合并到我自己的对象系统中。

    谢谢!

    已编辑以反映ref永远不会返回undef,只返回一个空字符串。

    编辑2 以下是一个跟进问题。以下有人建议使用:

    $self->isa(__PACKAGE__)
    

    但这不会一直成功吗?当然,除非呼叫者做了像以下那样愚蠢的事情:

    MyClass::MyMethod($ref_to_some_other_object)
    

3 个答案:

答案 0 :(得分:7)

首先,您应该croak而不是默默无闻,因为根据您的规范,将foo称为类方法是违反合同的行为。

其次,只是检查第一个参数是否是参考就足够了。如果涉及继承,您的方法将失败:

#!/usr/bin/perl

package A;
use Carp;

sub new { bless {} => shift }
sub foo {
    croak "I am not in " . __PACKAGE__ unless __PACKAGE__ eq ref(shift)
}

package B;

use base 'A';

package main;

$x = B->new;

$x->foo;
C:\Temp> t
I am not in A at C:\Temp\t.pl line 19

另见perldoc -f ref

  

如果引用的对象已被保存到包中,则返回该包名称。您可以将ref视为typeof运营商。

所以:

sub foo {
    croak "Don't call as class method" unless ref shift;
}

最后,请注意ref 从不 会返回undef

将此检查添加到每个方法是否是个好主意?我猜一个人可以通过契约观点从设计中得出这个论点。

另一方面,我的方法假设它们被称为实例方法,并且我只检查方法被调用为类方法的可能性,如果该方法在被调用时可以提供有意义的替代行为。

我不记得任何有这些检查的模块。

顺便说一句,而不是

sub foo {
    my ($self) = @_;

你应该使用

sub foo {
    my $self = shift;

只留下@_中方法的参数要解压缩。或者,你应该一举解开所有论点:

sub foo {
    my ($self, $bar, $baz) = @_;

答案 1 :(得分:4)

  

假设如果ref()返回不是undef它会返回当前包吗?

是否安全?

没有

my $bar = Bar->new;
Package::Foo::foo($bar);

将导致foo$bar置于$selfref $self将返回Bar

并且,正如前面的答案中已经提到的,检查文字包名而不是测试isa无论如何都会中断继承。

答案 2 :(得分:0)

你也可以使用isa的功能形式,这样你就不必检查以确保$ self是一个参考。当然,功能性isa有一个警告,包装不能覆盖isa,但我被撕裂,如果这是一个好的或坏的事情。在我自己的代码中,我通常做这样的事情,我发现它比UNIVERSAL :: isa有更多有用的调用语义。

sub isa {UNIVERSAL::isa @_ > 1 ? shift : $_, @_}

.....

return unless isa $self => __PACKAGE__;

for (@objects) {
    say $_->name if isa 'Package';
}