【问题标题】:perl constructor keyword 'new'perl 构造函数关键字'new'
【发布时间】:2014-06-08 12:32:51
【问题描述】:

我是 Perl 新手,目前正在学习 Perl 面向对象,并且偶然发现了编写构造函数。 看起来当使用new 作为子例程的名称时,第一个参数将是包名称。

构造函数必须使用关键字new吗?还是因为当我们使用包名调用new子程序时,传入的第一个参数是包名?

packagename->new;

当子程序有其他名称时,它的第一个参数将是对对象的引用?还是因为当通过对对象的引用调用子程序时,要传入的第一个参数将是对对象的引用?

$objRef->subroutine;

【问题讨论】:

  • 如果您还没有阅读“现代 Perl”,您可能会受益。 modernperlbooks.com/books/modern_perl_2014
  • Perl 中没有 new 关键字。它是对象构造函数的通用名称,许多类都以这种方式使用,但绝不是必需的。
  • 是的,你是对的:无论调用方法如何,invocant 始终是方法调用下的第一个参数(或第零个参数)。

标签: perl oop constructor method-invocation indirect-objects


【解决方案1】:

不是new的第一个参数,而是间接对象语法,

perl -MO=Deparse -e 'my $o = new X 1, 2'

被解析为

my $o = 'X'->new(1, 2);

来自perldoc

Perl 支持另一种方法调用语法,称为“间接对象”表示法。这种语法被称为“间接”,因为该方法出现在它被调用的对象之前。

话虽如此,new 不是构造函数调用的某种保留字,而是方法/构造函数本身的名称,在 perl 中不强制执行(即DBI 具有 connect 构造函数)

【讨论】:

  • 所以第一个参数是'X',它应该是子程序new的包名,因为新的子程序是使用'X'调用的?
  • @user2763829 是的,但是注意只有1和2是参数。
  • 这里调用的函数是模继承,将是X::new("X", 1, 2),因此将接收调用者作为其第零个参数。此外,关于间接对象方法调用的简短概要可能不足以让任何人知道它的实际含义。请参阅 perlglossary(1) 手册页。
  • This is about dative 英语中的宾语:indirect object:在英语语法中,a 之间的短名词短语动词及其直接宾语表示动作的受益人或接受者。在 Perl 中,print STDOUT "$foo\n"; 可以理解为“动词间接对象对象”,其中 STDOUT 是打印操作的接收者,$foo 是正在打印的对象。同样,在调用方法时,您可以将调用者放置在方法及其参数之间的 dative slot$gollum = new Pathetic::Creature "Sméagol"; give $gollum "Fisssssh!"; give $gollum "Precious!";
  • 间接对象槽:使用间接对象调用语法时,位于方法调用与其参数之间的语法位置。 (该槽的区别在于它与下一个参数之间没有逗号。)STDERR 位于此处的间接对象槽中:print STDERR "Awake! Awake! Fear, Fire, Foes! Awake!\n";
【解决方案2】:

注意:出于教学目的,以下所有示例均已简化。

关于方法

是的,你是对的。 new 函数的第一个参数,if 作为方法调用,将是你调用它的对象。

调用方法有两种“风格”,但结果都是一样的。一种风格依赖于运算符,二进制-> 运算符。另一种风格依赖于参数的顺序,即bitransitive verbs work in English。大多数人只在内置函数中使用与格/双及物风格——也许在构造函数中使用,但很少使用其他任何东西。

在大多数(但不是全部)情况下,前两个是等价的:

1. 与格方法调用

这是位置性的,它使用词序来确定发生了什么。

use Some::Package;
my $obj1 = new Some::Package NAME => "fred"; 

注意我们没有使用方法箭头:没有写的->。这就是 Perl 本身与许多自己的函数一起使用的,比如

 printf STDERR "%-20s: %5d\n", $name, $number;

几乎每个人都喜欢同类产品:

 STDERR->printf("%-20s: %5d\n", $name, $number);

然而,如今这种与格调用几乎只用于内置函数,因为人们总是把事情搞糊涂。

2。 箭头方法调用

箭头调用在很大程度上更清晰、更干净,不太可能让你陷入 Perl 解析奇怪的杂草中。注意我说的是不太可能;我确实没有说它没有任何不妥之处。但是为了回答这个问题,我们就假装这样吧。

use Some::Package;
my $obj2 = Some::Package->new(NAME => "fred");

在运行时,除非有任何奇特的奇怪或继承问题,否则实际的函数调用将是

 Some::Package::new("Some::Package", "NAME", "fred");

例如,如果您在 Perl 调试器中并进行了堆栈转储,那么它的调用链中将包含与前一行类似的内容。

由于调用方法总是在参数列表前加上 invocant,所有将作为方法调用的函数都必须考虑到那个“额外”的第一个参数。这很容易做到:

package Some::Package;
sub new {
   my($classname, @arguments) = @_;
   my $obj = { @arguments };
   bless $obj, $classname;
   return $obj;
}

这只是调用构造函数的最常见的新方法的一个极其简化的示例,以及内部发生的情况。在实际的生产代码中,构造函数会更加小心。

方法和间接

有时你在编译时不知道类名或方法名,所以你需要使用一个变量来保存其中一个或另一个,或两者兼而有之。编程中的间接与自然语言中的间接对象不同。间接只是意味着您有一个包含其他内容的变量,因此您可以使用该变量来获取其内容。

print 3.14;    # print a number directly

$var = 3.14;   # or indirectly
print $var;

我们可以使用变量来保存方法调用中涉及的其他内容,而不仅仅是方法的参数。

3.使用间接方法名称的箭头调用:

如果你不知道方法名,那么你可以把它的名字放在一个变量中。仅使用箭头调用来尝试此操作,而不是与格调用。

use Some::Package;
my $action = (rand(2) < 1) ? "new" : "old";
my $obj    = Some::Package->$action(NAME => "fido");

这里方法名称本身在运行时是未知的。

4.使用间接名称的箭头调用:

这里我们使用一个变量来包含我们想要使用的类的名称。

my $class = (rand(2) < 1) 
              ? "Fancy::Class" 
              : "Simple::Class";
my $obj3 = $class->new(NAME => "fred");

现在我们随机选择一类或另一类。

您实际上也可以通过这种方式使用与格调用:

my $obj3 = new $class NAME => "fred";

但这通常不是通过用户方法完成的。不过,有时它确实会发生在内置插件中。

my $fh = ($error_count == 0) ? *STDOUT : *STDERR;
printf $fh "Error count: %d.\n", $error_count;

这是因为尝试在与格槽中使用表达式通常不会在没有块的情况下起作用;否则它只能是一个简单的标量变量,甚至不能是数组或散列中的单个元素。

printf { ($error_count == 0) ? *STDOUT : *STDERR } "Error count: %d.\n", $error_count;

或者更简单地说:

print { $fh{$filename} } "Some data.\n";

真是太丑了。

让调用者小心

请注意,这并不完美。与格对象槽中的文字与那里的变量不同。例如,使用 literal 文件句柄:

print STDERR;

意思

print STDERR $_;

但是如果你使用 indirect 文件句柄,像这样:

print $fh;

这实际上意味着

print STDOUT $fh;

这不太可能意味着你想要的,可能是这样的:

print $fh $_;

又名

$fh->print($_);

高级用法:双重性质方法

关于方法调用箭头-&gt; 的一点是,它不知道它的左侧操作数是表示类名的字符串还是表示对象实例的祝福引用。

当然,没有什么正式要求$class 包含包名。可能是两者之一,如果是,则由方法本身来做正确的事情。

use Some::Class;

my $class = "Some::Class";
my $obj   = $class->new(NAME => "Orlando"); 

my $invocant = (rand(2) < 1) ? $class : $obj;
$invocant->any_debug(1);

这需要一个非常花哨的any_debug 方法,根据调用者是否受到祝福来做不同的事情:

package Some::Class;
use Scalar::Util qw(blessed);

sub new {
   my($classname, @arguments) = @_; 
   my $obj = { @arguments };
   bless $obj, $classname;
   return $obj;
}   

sub any_debug {
    my($invocant, $value) = @_;
    if (blessed($invocant)) {
        $invocant->obj_debug($value);
    } else {
        $invocant->class_debug($value);
    }
}

sub obj_debug {
    my($self, $value) = @_;
    $self->{DEBUG} = $value;
}

my $Global_Debug;
sub class_debug {
    my($classname, $value) = @_;
    $Global_Debug = $value;
}

但是,这是一种相当先进和微妙的技术,仅适用于少数不常见的情况。不建议在大多数情况下使用它,因为如果处理不当,可能会造成混淆——即使处理不当也是如此。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-21
    • 1970-01-01
    • 2016-10-29
    • 1970-01-01
    • 2022-06-13
    • 2020-05-12
    相关资源
    最近更新 更多