为什么我不应该使用UNIVERSAL :: isa?

时间:2008-10-15 10:57:47

标签: perl

根据这个

http://perldoc.perl.org/UNIVERSAL.html

我不应该使用UNIVERSAL :: isa()而应该使用$ obj-> isa()或CLASS-> isa()。

这意味着要首先找出某个东西是引用,然后引用这个类,我必须这样做

eval { $poss->isa("Class") }

并检查$ @和所有gumph,否则

use Scalar::Util 'blessed';
blessed $ref && $ref->isa($class);

我的问题是为什么? UNIVERSAL :: isa有什么问题?对于像以下这样的东西来说更清洁:

my $self = shift if UNIVERSAL::isa($_[0], __PACKAGE__)

查看是否在对象上调用此函数。还有一个很好的清洁替代品,不会因为&符号和长线而变得麻烦吗?

7 个答案:

答案 0 :(得分:33)

主要问题是,如果直接调用UNIVERSAL::isa,则会绕过任何已超载isa的类。如果这些类依赖于重载行为(他们可能会这样做,或者他们不会覆盖它),那么这就是一个问题。如果你直接在你的祝福对象上调用isa,那么在任何一种情况下都会调用正确的isa方法(如果存在则重载,如果不存在则重载)。

第二个问题是UNIVERSAL::isa只会像对isa的其他使用一样,在祝福的引用上执行您想要的测试。它对于非祝福引用和简单标量具有不同的行为。因此,不检查$ref是否受祝福的示例不是正确的做法,您忽略了错误条件并使用UNIVERSAL的替代行为。在某些情况下,这可能会导致细微的错误(例如,如果您的变量包含类的名称)。

考虑:

use CGI;

my $a = CGI->new();

my $b = "CGI";

print UNIVERSAL::isa($a,"CGI");  # prints 1, $a is a CGI object.
print UNIVERSAL::isa($b,"CGI");  # Also prints 1!! Uh-oh!!

因此,总而言之,请勿使用UNIVERSAL::isa ...执行额外的错误检查并直接在对象上调用isa

答案 1 :(得分:9)

请参阅UNIVERSAL::isaUNIVERSAL::can的文档,了解您不应该这样做的原因。

简而言之,有一些重要的模块真正需要覆盖'isa'(例如Test::MockObject),如果你把它称为函数,你就会打破这个。

我不得不说,my $self = shift if UNIVERSAL::isa($_[0], __PACKAGE__)看起来并不是非常干净 - 反Perl的倡导者会抱怨线路噪音。 :)

答案 2 :(得分:7)

要直接回答您的问题,答案就在您链接到的页面底部,即如果包定义了isa方法,则直接调用UNIVERSAL::isa将不会调用包{ {1}}方法。从面向对象的角度来看,这是非常不直观的行为。

这篇文章的其余部分只是关于你为什么要这样做的更多问题。

在上面这样的代码中,特定isa测试在什么情况下会失败?即,如果它是一个方法,在这种情况下,第一个参数不是包类或其实例?

我问这个是因为我想知道你是否有合理的理由想要先测试第一个参数是否是一个对象。也就是说,你只是试图抓住人们说isa而不是FooBar::methodFooBar->method?我猜Perl不是为那种溺爱而设计的,如果人们错误地使用$foobar->method,他们很快就会发现。

您的里程可能会有所不同。

答案 3 :(得分:7)

其他人告诉你为什么你不想使用UNIVERSAL::isa,因为它会在事情超载isa时中断。如果他们已经习惯了超载那种非常特殊的方法,你当然希望尊重它。当然,你可以通过写:

来做到这一点
if (eval { $foo->isa("thing") }) {
     # Do thingish things
}

因为eval保证在抛出异常时返回false,否则返回最后一个值。但是看起来很糟糕,你不应该用有趣的方式编写代码,因为语言需要你。我们真正想要的只是写:

if ( $foo->isa("thing") ) {
     # Do thingish things
}

要做到这一点,我们必须确保$foo始终是一个对象。但是$foo可以是字符串,数字,引用,未定义的值或各种奇怪的东西。多么可惜Perl无法将所有作为第一类对象。

哦,等等,它可以......

use autobox;   # Everything is now a first class object.
use CGI;       # Because I know you have it installed.

my $x = 5;
my $y = CGI->new;

print "\$x is a CGI object\n" if $x->isa('CGI');   # This isn't printed.
print "\$y is a CGI object\n" if $y->isa('CGI');   # This is!

您可以从CPAN中抓取autobox。您也可以将它与词法范围一起使用,因此所有内容都可以是一个第一类对象,仅用于您想要使用->isa()的文件或块,而不会产生额外的麻烦。它比我在这个简单的例子中所涵盖的更多 lot

答案 4 :(得分:2)

假设你想要做的事情的例子是在一个对象方法中,你就是不必要的偏执狂。第一个传递的项将始终是对相应类(或子类)的对象的引用,或者它将是类(或子类)的名称。它永远不会是任何其他类型的引用,除非该方法被故意称为函数。因此,您可以安全地使用 ref 来区分这两种情况。

if (ref $_[0]) {
  my $self = shift;
  # called on instance, so do instancey things
} else {
  my $class = shift;
  # called as a class/static method, so do classy things
}

答案 5 :(得分:0)

右。对于重载isa的类,这是错误的。只需使用以下习语:

if (eval { $obj->isa($class) }) {

易于理解和普遍接受。

答案 6 :(得分:0)

2020 年更新:Perl v5.32 具有 class infix operatorisa,可处理左侧的任何类型的事情。如果 $something 不是一个对象,你会返回 false 而不会爆炸。

use v5.32;

if( $something isa 'Animal' ) { ... }