【问题标题】:What's the correct way to deal with multiple inheritence of modules sharing a common "ancestor" in Perl?在 Perl 中处理共享公共“祖先”的模块的多重继承的正确方法是什么?
【发布时间】:2021-09-17 22:37:55
【问题描述】:

(Moose/Moo 的答案当然是“角色”。这个问题是关于您想要组合两个模块的一般情况,这两个模块都是同一个父级的子类,假设没有 Moose/Moo。)

让我们举一个稍微做作的例子:模块LWP::UserAgent::DeterminedLWP::RobotUA 都是LWP::UserAgent 的子类,并以不同的方式扩展它。如果我想创建一个结合了两者方法的对象,我应该怎么做?它的核心仍然是一个LWP::UserAgent 对象,并且其他两个模块不会相互冲突,所以应该很容易,对吧?

据我所知,正确的做法是创建一个新包,将其他两个都声明为父包 - use parent qw(LWP::RobotUA LWP::UserAgent::Determined) - 然后从中创建对象。而且,确实,如果你这样做,你会得到一个对象,其中包含来自两者的方法,以及来自基类LWP::UserAgent 的方法,并且几乎一切都如你所愿。 p>

但不完全是。 LWP::UserAgent::DeterminedLWP::RobotUA 都具有某些属性的默认值,如果没有给出其他值,则在创建对象时设置这些属性。将两者结合起来时,LWP::RobotUA 的默认值被设置,但 不是 LWP::UserAgent::Determined 的。所以肯定有问题。

这是一些测试代码:

#!/usr/bin/env perl

use strict;
use warnings;
use 5.016;

use LWP::RobotUA;
use LWP::UserAgent::Determined;

package MyUA;
use parent qw(LWP::RobotUA LWP::UserAgent::Determined);

for my $module (qw(LWP::RobotUA LWP::UserAgent::Determined MyUA)) {
    say '# ', $module, ' #';
    my $ua = $module->new(
                          'agent' => 'Test-UA',
                          'from'  => 'example@example.com',
                         );
    my $req = HTTP::Request->new(GET => 'https://www.bbc.co.uk/emp/network_status.txt');
    my $response = $ua->request($req);
    unless ($module eq 'LWP::UserAgent::Determined') {
        say 'Use sleep? : ', $ua->use_sleep() // 'not defined!';
        say 'Allowed OK? : ', $ua->rules->allowed('https://www.bbc.co.uk/') // 'not defined!';
        say 'Sites with rules: ', (defined $ua->rules()->{loc}) ? join(', ', (sort keys %{$ua->rules()->{loc}})) : 'not defined!';
    }
    unless ($module eq 'LWP::RobotUA') {
        print 'Timings: ';
        if (defined $ua->timing()) {
            say $ua->timing();
        }
        else {
            print 'Timing defaults not set! ';
            $ua->timing('1,5,10,20,60,240');
            say '...but the method works: ', $ua->timing();
        }
        say 'Retry codes: ', (defined $ua->codes_to_determinate()) ? join(', ', (sort keys %{$ua->codes_to_determinate()})) : 'not defined!';
    }
    say '#'x60;
}

这个输出:

# LWP::RobotUA # 使用睡眠? : 1 允许好吗? : 1 有规则的网站:www.bbc.co.uk:443 ################################################## ########## # LWP::UserAgent::Determined # 时间:1,3,15 重试代码:408、500、502、503、504 ################################################## ########## #我的UA# 使用睡眠? : 1 允许好吗? : 1 有规则的网站:www.bbc.co.uk:443 计时:未设置计时默认值! ...但该方法有效:1,5,10,20,60,240 重试代码:未定义! ################################################## ##########

在这里你可以看到两个模块的方法都有效,但是LWP::UserAgent::Determinedtiming()codes_to_determinate()方法与LWP::RobotUA结合时没有设置默认值,而LWP::RobotUA的@987654340 @ 方法 使用其默认值 1 创建的。但是,手动设置值可以正常工作,否则组合对象会按预期工作。

所以,总而言之:处理这种情况的正确方法是什么,您想将两个模块合并为一个共同的第三个子类?事实上,这是否正确,但我只是选择了一个不幸的示例,LWP::UserAgent::Determined 在设置默认值方面表现不佳?

【问题讨论】:

  • 我还没有阅读任何代码,但我怀疑new 方法都没有调用SUPER 或类似的方法。所以一半的代码没有被调用。

标签: perl oop lwp-useragent


【解决方案1】:

您的新类实际上如下所示:

package MyUA;

use parent qw(LWP::RobotUA LWP::UserAgent::Determined);

1;

让我们像这样测试它:

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';

use MyUA;

my $ua = MyUA->new(
  agent => 'test',
  from  => 'me@example.com',
);

say ref $ua;

这告诉我们我们有“MyUA”对象。但它到底是什么?我们做了什么?

嗯,对象是使用构造方法构建的。这(通常)称为new()。在这种情况下,您尚未在类中定义 new() 方法。所以 Perl 会在超类中寻找方法。它通过搜索在@INC 中找到的类并查看每个类是否依次包含new() 方法来实现。

你的两个超类都有一个new() 方法。但是 Perl 只需要一个。因此,当它找到一个时,它会停止查找并调用该方法。它调用的第一个是 LWP::RobotUA 中的那个(因为这是传递给use parent 的列表中的第一个)。这样就会被调用。

这意味着您实际上在这里得到的是一个 LWP::RobotUA 类的对象。嗯,大部分。它被祝福到正确的类中,如果你调用 LWP::UserAgent::Determined 中的任何方法,但 LWP::RobotUA 中没有,它仍然可以工作。但是没有调用任何 LWP::UserAgent::Determined 初始化代码。

这很好地说明了为什么多重继承是一个坏主意。除了最微不足道的情况外,很难在所有情况下都做到这一点。

我不能在这里给你答案。因为只有您知道您需要两个超类中的哪些位。但解决方案将涉及将您自己的 new() 方法添加到您的类中,并可能从其中调用两个超类构造函数。

更新:好的,我仔细看了一遍。而且可能比我想象的要容易。

LWP::RobotUA::new() 在做各种其他事情的过程中打电话给LWP::UserAgent::new()。但是LWP::UserAgent::Determined::new() 在其处理开始时调用LWP::UserAgent::new(),然后将其所有其他初始化捆绑在一个名为_determined_init() 的单独方法中。

所以看起来您的解决方案可能很简单,就像添加这样的构造方法:

sub new {
  my $class = shift;
  my $self = $class->SUPER::new(@_);
  $self->_determined_init();

  return $self;
}

$class->SUPER::new() 的调用调用LWP::RobotUA::new(),因为这是@INC 中的第一个类。反过来,这会调用LWP::UserAgent::new() - 这样就完成了初始化。然后我们只需要调用_determined_init() 来初始化另一个超类。

它似乎适用于我的(非常基本的)测试。但是我还是很怀疑多重继承:-)

更新 2: 是的。 ikegami is right。我的解决方案只解决了构造对象的问题。我没有考虑实际使用它。

【讨论】:

  • 你可以很容易地为new 捏造一个解决方案,但对于simple_request 来说它有点棘手。请参阅我的答案以获得解决方案。
  • "但我仍然对多重继承持怀疑态度 :-)" 需要明确的是,这个问题并不是要以任何方式支持多重继承,而且我永远不会使用它任何严肃的背景! (我说这是一个人为的例子。?)更多的是我在阅读文档,听起来它应该工作——即使你不应该这样做它! ——几乎做到了。然后我想知道为什么。
  • 我接受了这个答案,因为它巧妙地解决了有问题的示例(幸运的是 LWP::UserAgent::Determined 它使用了 _determined_init()!)但是我从阅读你和 ikegami 的文章中受益匪浅一起回答。谢谢!
  • Re "我接受这个答案,因为它巧妙地解决了相关示例",它没有。它提供了一个正确构造对象的解决方案(就像我的解决方案一样),但该对象不起作用。你需要我的解决方案。戴夫的解决方案不起作用是我写答案的全部原因。
【解决方案2】:

如果不对 LWP::UA::Determined 和/或 LWP::RobotUA 进行更改,这将无法工作。

但是这些更改可以通过猴子补丁来应用。解决方案显示在底部。


问题

LWP::UA::Determined 包裹->simple_request,也就是说它提供了一个->simple_request 调用->SUPER::simple_request(即->LWP::UA::simple_request

LWP::RobotUA 包装了->simple_request,也就是说它提供了一个->simple_request 调用->SUPER::simple_request(即->LWP::UA::simple_request

当您拨打->request 时,您最终会拨打->simple_request。根据@ISA 中模块的顺序,这将调用LWP::UA::Determined::simple_request(然后是LWP::UA::simple_request)或LWP::RobotUA::simple_request(然后是LWP::UA::simple_request)。

它不会同时调用LWP::UA::Determined::simple_requestLWP::RobotUA::simple_request。如果这样做,它将无法正常工作,因为这最终会调用 LWP::UA::simple_request 两次。

->new 也存在完全相同的问题。

演示

use feature qw( say );

{ package Base;
  sub method { say __PACKAGE__; } }

{ package SubA; our @ISA = qw( Base ); 
  sub method { say __PACKAGE__; $_[0]->SUPER::method(); } }

{ package SubB; our @ISA = qw( Base ); 
  sub method { say __PACKAGE__; $_[0]->SUPER::method(); } }

{ package SubAB; our @ISA = qw( SubA SubB ); }

SubAB->method();  # SubA Base

解决方案

LWP::UA::Determined 和 LWP::RobotUA 需要使用 next::method 而不是 SUPER(或者它们需要完全不同的设计)才能有机会工作。

演示

use feature qw( say );

{ package Base;
  sub method { say __PACKAGE__; } }

{ package SubA; use mro 'c3'; our @ISA = qw( Base );
  sub method { say __PACKAGE__; $_[0]->next::method(); } }

{ package SubB; use mro 'c3'; our @ISA = qw( Base );
  sub method { say __PACKAGE__; $_[0]->next::method(); } }

{ package SubAB; use mro 'c3'; our @ISA = qw( SubA SubB ); }

SubAB->method();  # SubA SubB Base

解决方法

所有希望都不会消失!我们可以修补 LWP::UA::Determined 和 LWP::RobotUA 以获得所需的行为!

只需将其添加到您的程序中:

use LWP::RobotUA qw( );
BEGIN {
   package LWP::RobotUA::Inserted;
   use mro 'c3';
   our @ISA = @LWP::RobotUA::ISA;
   sub new            { return $_[0]->next::method(); }
   sub simple_request { return $_[0]->next::method(); }
   sub agent          { return $_[0]->next::method(); }
   @LWP::RobotUA::ISA = __PACKAGE__;
}

use LWP::UserAgent::Determined qw( );
BEGIN {
   package LWP::UserAgent::Determined::Inserted;
   use mro 'c3';
   our @ISA = @LWP::UserAgent::Determined::ISA;
   sub new            { return $_[0]->next::method(); }
   sub simple_request { return $_[0]->next::method(); }
   @LWP::UserAgent::Determined::ISA = __PACKAGE__;
}

您还需要在自己的班级中使用use mro 'c3';(或仔细选择@ISA 的顺序)才能使->agent 正常工作。

演示

# Insert code from first demo here.

{ package SubA::Inserted; use mro 'c3'; our @ISA = @SubA::ISA;
  sub method { return $_[0]->next::method(); }
  @SubA::ISA = __PACKAGE__; }

{ package SubB::Inserted; use mro 'c3'; our @ISA = @SubB::ISA;
  sub method { return $_[0]->next::method(); }
  @SubB::ISA = __PACKAGE__; }

SubAB->method();  # SubA SubB Base

【讨论】:

  • 很遗憾我只能接受一个答案,因为感觉就像你和戴夫一起全面涵盖了一切。谢谢,我在这里学到了很多东西!
  • 但戴夫的回答没有提供解决方案 X_X
猜你喜欢
  • 2020-03-14
  • 2013-06-15
  • 1970-01-01
  • 2015-01-02
  • 1970-01-01
  • 2011-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多