Perl:如何在多重继承中调用特定方法?

Perl: How to call a specific method in multiple inheritance?

我有一个Perl包,它使用另外两个包作为它的基础。

父母一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package Parent1;

use strict;
use warnings;

sub foo
{
    my $self = shift;
    print ("
 Foo from Parent 1"
);
    $self->baz();
}

sub baz
{
    my $self = shift;
    print ("
 Baz from Parent 1"
);
}
1;

亲本2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package Parent2;

use strict;
use warnings;

sub foo
{
    my $self = shift;
    print ("
 Foo from Parent 2"
);
    $self->baz();
}

sub baz
{
    my $self = shift;
    print ("
 Baz from Parent 2"
);
}
1;

孩子:这将使用上面的两个父包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Child;

use strict;
use warnings;

use base qw(Parent1);
use base qw(Parent2);

sub new
{
    my $class = shift;
    my $object = {};
    bless $object,$class;
    return $object;
}
1;

主营:

1
2
3
4
5
6
7
use strict;
use warnings;
use Child;

my $childObj = new Child;

$childObj->Parent2::foo();

输出:

1
2
 Foo from Parent 2
 Baz from Parent 1

我的分析:

从输出中可以清楚地看到,子对象被传递给parent2的foo方法,并且从该foo方法调用baz方法。它首先检查子包中的baz方法,因为我用子对象调用它。因为baz方法不在子包中,所以它检查基类中的方法。parent1是child的第一个基类。因此,它在parent1中找到该方法并调用parent1的baz方法。

我的问题:

是否可以在不更改子类中基类的顺序的情况下调用parent2的baz方法?

我的预期输出:

1
2
 Foo from Parent 2
 Baz from Parent 2

上面的例子只是我实际问题的一个类比。我没有修改基类的权限。我只能修改子类。那么,是否可以在不改变基类顺序的情况下,以这样一种方式更改子类,即它从parent2中获取两个方法?

谢谢!


如果您有权修改基类,您可以通过将$self->baz()更改为Parent2::baz($self)来实现这一点,但您说过不能这样做。

既然这不是一个选项,您对临时更改基类顺序有什么看法?在Perl中,基类列表实际上只是每个包中名为@ISA的数组,因此可以使用local在一个块中创建该数组的本地化副本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env perl    

use strict;
use warnings;
use 5.010;

package Parent1;

sub foo { say 'foo1'; $_[0]->baz; }
sub baz { say 'baz1'; }

package Parent2;

sub foo { say 'foo2'; $_[0]->baz; }
sub baz { say 'baz2'; }

package Child;

use base qw( Parent1 Parent2 );

sub new { return bless {} }

package main;

my $childobj = Child->new;
{
  local @Child::ISA = qw( Parent2 Parent1 );
  say 'In localized block';
  $childobj->Parent2::foo;
}

say 'Block has exited';
$childobj->Parent2::foo;

输出:

1
2
3
4
5
6
In localized block
foo2
baz2
Block has exited
foo2
baz1

因此,您可以看到,这在块内提供了本地化@ISA的所需输出,然后在块退出时恢复原始行为。

另外,结束语中的一个旁注:在Perl中使用new Child被称为"间接对象表示法",通常被认为是一件坏事。我建议用Child->new代替。


您可以重写Child类中所需的方法,并分派到完全限定的调用

1
2
3
4
5
6
7
8
9
10
11
package Child;

...

sub baz
{
    shift;
    Parent2::baz(@_);
}

...

添加到代码中后,它将根据需要打印,使用Baz from Parent2

这是一种相当手工的方法来"扩充"这些类的接口,显然这些类并不是为多重继承而设计的。但您描述的情况确实令人不快,必须这样做,或者对特定于硬代码的@ISA操作。

更复杂的需求见MRO,与所有这些都有明确的关联。它支持一些内省,这样您就可以在运行时做出决定。你还是要用Child::baz()来做。

你能改变设计而不使用多重继承吗?


可以为首选方法创建别名。一般来说,除非你知道自己在做什么,否则这是个坏主意。查看perldoc perlmod符号表。

将主文件更新为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 use strict;
 use warnings;
 use Child;

 my $childObj = new Child;

 $childObj->Parent2::foo();

 print"------
"
;
 no warnings; # Otherwise will complain about 'Name"Child::baz" used only once: possible typo'
 # Create an alias
 *Child::baz = *Parent2::baz;
 use warnings;
 $childObj->Parent2::foo();

产量

1
2
3
4
5
Foo from Parent 2
Baz from Parent 1
------
Foo from Parent 2
Baz from Parent 2