Perl多重继承,两次继承相同的方法名:如何正确使用正确的方法?

Perl multiple inheritance, inheriting the same method name twice: How to use the right method correctly?

提问人:U. Windl 提问时间:7/28/2023 最后编辑:U. Windl 更新时间:7/30/2023 访问量:46

问:

好吧,我不应该使用使用多重继承的Perl,但我做到了。 请考虑以下代码草图(表示当前对象):$self

package A;
sub f { ... }
sub _init { ...; $self->f; ... }
sub new { ... $self->_init ... }

package B;
sub _init { ... }
sub new { ... $self->_init ... }

package C;
sub f { ... }
our @ISA = qw(A B);
sub new
{
    my $class = $_[0];
    my $self = ...;
    bless $self, $class;
    ??? call A:_init using $self, call B:_init using $self, setup C's attributes
}

面临的挑战是如何集成 from 和 的构造函数(假设完成除祝福和分配对象之外的所有设置)。 using 只能使用两个继承方法中的一个,并且不是很明显(可能来自第一个列出的包)。C->newAB_init$self->SUPER::_init@ISA

另一件事是如何确保使用动态绑定,即:使用?具体使用。 具体来说也可以有自己的,甚至可以继承更多的包......A::_initfC::fA::_init($self)C_initAB

可编译示例

下面是一个编译的代码示例,它演示了应该发生什么(注释的语句已经来自 https://stackoverflow.com/a/76786760/6607497):

#!/usr/bin/perl
require 5.018_000;
use strict;
use warnings;

package P1;

sub f($)
{
    $_[0]->[0] = 1;
}

sub _init($)
{
    $_[0]->f();
}

sub new($)
{
    my $class = $_[0];
    my $self = [];

    $#$self = 0;
    bless $self, $class;
    $self->_init();
    return $self;
}

package P2;

sub _init($)
{
    $_[0]->[0] = 2;
}

sub new($)
{
    my $class = $_[0];
    my $self = [];

    $#$self = 0;
    bless $self, $class;
    _self->_init();
    return $self;
}

package P3;

our @ISA = qw(P1 P2);

BEGIN {
    use Exporter qw(import);
}

sub f($)
{
    $_[0]->[1] = 3;
}

sub _init($)
{
    $_[0]->[2] = 4;
}

sub new($)
{
    my $class = $_[0];
    my $self = [];

    $#$self = 2;
    bless $self, $class;
    #$self->P1::_init();
    #$self->P2::_init();
    $self->_init();
    return $self;
}

package min;

my $o = P3->new();
$DB::single = 1;
print $o, "\n"; # prevent optimizing $o away too early

所以当 sets , sets , so 's 时,当从对象调用时,不应该设置 ,而是设置 。P1::f$self->[0] = 1P3::f$self->[1] = 3P1_initP3$self->[0] = 1$self->[1] = 3

所以结果应该是:

  DB<1> x $o
0  P3=ARRAY(0xbd27e8)
   0  2
   1  3
   2  4
Perl 方法 构造函数 多重继承

评论

0赞 tobyink 7/28/2023
请注意,“B”不是在示例中使用的最佳类名,因为有一个名为“B”的核心 Perl 模块。
0赞 U. Windl 7/28/2023
实际的类名不是 、 和 ,但为了说明起见,它应该没问题。ABC

答:

2赞 tobyink 7/28/2023 #1

子类可以执行以下操作:

package C;

our @ISA = qw(A B);

sub new {
    my $class = $_[0];
    my $self = ...;
    bless $self, $class;
    $self->A::_init();
    $self->B::_init();
    return $self;
}

如果 C 有自己的方法,我会这样写:_init

package C;

our @ISA = qw(A B);

sub new {
    my $class = $_[0];
    my $self = ...;
    bless $self, $class;
    $self->_init;
    return $self;
}

sub _init {
    my $self = shift;
    $self->A::_init();
    $self->B::_init();
    ...; # more initialization
}

您关心的问题之一似乎是确保使用动态绑定。如果使用箭头运算符 () 调用方法,它将始终使用动态绑定。$object->f->

因此,如果调用(静态绑定)但在调用中发生,则该调用将使用动态绑定。A::_init($object)A::_init$self->f

所以在我的例子中,为什么我用 instead of ?因为前一种语法使用动态绑定,所以它只是从“A”开始方法解析,而不是从“C”开始。$self->A::_init()A::_init($self)

一个更复杂的例子:

package Grandparent {
  use Class::Tiny;  # provides `new`

  sub init {
    my $self = shift;
    print "From Grandparent\n";
  }
}

package Parent1 {
  use parent -norequire, 'Grandparent';  # provides `@ISA`
}

package Parent2 {
  use Class::Tiny;

  sub init {
    my $self = shift;
    print "From Parent2\n";
  }
}

package Child {
  use parent -norequire, 'Parent1', 'Parent2';

  sub init {
    my $self = shift;
    $self->Parent1::init();
    $self->Parent2::init();
    print "From Child\n";
  }
}

my $eg = Child->new;
$eg->init;

注意:在此示例中,继承自两个父类。它的方法通过其每个父级调用该方法,包括未定义自己的 init 方法但继承自 的类。ChildinitinitParent1Grandparent

评论

0赞 U. Windl 7/28/2023
好消息是你的答案有效;坏消息是,在我原来的程序中,一定有一个不同的错误(仍有待发现);-)
1赞 clamp 7/30/2023 #2

虽然可以在类层次结构中显式调用不同的方法,但这似乎是一种反模式。如果层次结构中的某个位置发生更改,则需要调整其他位置的代码,以保持程序按预期工作。init()@ISA

Perl 在 core 中有一个叫做 mro 的模块,它提供了在 dfs(默认)和 c3 之间切换类方法解析顺序的可能性。 即使你想将 mro 保留在 dfs,你也可以使用 next::method() 和 maybe::next method() 它们总是使用 c3 mro 来解析方法调用。

下面的示例添加一个公共父类,其中包含按正确顺序调用的可继承构造函数和 init 方法。请注意,通常首先让 init 代码在父类中完成工作。

use strict;
use warnings;
use feature 'say';
use mro;
package GP;

sub new($)
{
    my $class = $_[0];
    my $self = [];
    bless $self, $class;
    $self->_init();
    return $self;
}

sub _init
{
    say 'GP::init called';
    say 'GP::init: do work';
}

package P1;
our @ISA = 'GP';
sub f($)
{
    say 'P1::f called';
}

sub _init($)
{
    my $self = shift;
    say 'P1::init called';
    $self->maybe::next::method(@_);
    say 'P1::init: do work, call ->f()';
    $self->f();
}


package P2;
our @ISA = 'GP';
sub _init($)
{
    my $self = shift;
    say 'P2::init called';
    $self->maybe::next::method(@_);
    say 'P2::init: do work';
}

package P3;

our @ISA = qw(P1 P2);

sub f($)
{
    say 'P3::f called';
}

sub _init($)
{
    my $self = shift;
    say 'P3::init called';
    $self->maybe::next::method(@_);
    say 'P3::init: do work';
}

package main;

my $o = P3->new;


# P3::init called
# P1::init called
# P2::init called
# GP::init called
# GP::init: do work
# P2::init: do work
# P1::init: do work, call ->f()
# P3::f called
# P3::init: do work

评论

0赞 U. Windl 7/31/2023
"虽然可以在类层次结构中显式调用不同的 init() 方法,但这似乎是一种反模式。如果你的@ISA在层次结构中的某个地方发生了变化,你需要调整其他地方的代码,以保持你的程序按预期工作。