从派生类对象中返回基类对象

时间:2020-03-04 10:26:36

标签: perl oop inheritance

当我想将此机制应用于派生类的Perl对象时,我意识到Perl不是静态类型的:

说我有一个基类B和一个派生类D继承自B。 另外,我还有一个对象$obj,它保存着一个D对象。 函数Bf()期望的参数类型为B

显然(根据多态性规则),我可以像$obj一样将Bf()传递给Bf($obj),但是与静态类型的语言Bf()不同的是,整个{ {1}}对象(而不仅仅是D的元素)。

在Perl中是否有(相当干净和简单)的解决方案?解决方案应“隐藏” BBD不具有的属性(和方法),而不是限制对原始Bf()(即{{ 1}}实际上。

仅限成人程序员(自2020年3月6日添加)

好的,人们想要更具体的描述。 不幸的是(如前所述),原始程序非常复杂,并使用类似反射的机制自动生成getter,setter和formatter,我真的不能在此给出一个最低限度的示例,因为它会不小。

首先,我有一个处理消息的类B(毫不奇怪!)。 然后,我有一个函数D,该函数期望MessageHandler对象作为第一个参数。

然后我有这个类的层次结构(实际上要复杂得多):

log_message($$$)

现在,如果MessageHandler想要MessageHandler ControlMessageHandler (ISA: MessageHandler) ControlMessageResponseHandler (ISA: ControlMessageHandler) ,我可以通过log_message,因为它符合MessageHandler。 但是这样做会将ControlMessageResponseHandler中不存在的MessageHandlerControlMessageResponseHandler的所有属性都暴露了。

危险是log_message可能(错误地)访问MessageHandler中不存在的log_message属性。为了防止出现错误,我想防止这种情况发生,或者至少要得到一些警告(例如,我会使用像Eiffel这样的静态类型语言)。

里面的肮脏细节

只要有问题,我将概述如何构建数组对象(一个工作示例将需要大量额外的代码):

首先,像这样自动分配数组索引:

ControlMessageResponseHandler
继承需要

MessageHandler。 属性的定义如下:

use constant I_VERBOSITY    => IS_NEXT->(); # verbosity level
use constant I_TAG          => IS_NEXT->(); # additional tag
use constant I_TAG_STACK    => IS_NEXT->(); # tag stack
use constant I_MSG_DEBUG    => IS_NEXT->(); # handler for debug messages
...
use constant I_LAST         => IS_LAST->(); # last index (must be last)

该定义包含有关如何格式化每个属性的提示。 此信息用于设置格式化程序,以格式化每个属性,如下所示:

I_LAST

字母和二传手的定义如下:

use constant ATTRIBUTES => (
    ['verbosity', I_VERBOSITY, undef],
    ['tag', I_TAG, \&Class::_format_string],
    ['tag_stack', I_TAG_STACK, undef],
    ['msg_debug', I_MSG_DEBUG, \&Class::_format_code],
...
);

构造函数将使用以下行:

use constant FORMATTERS =>
    (map { Class::_attribute_string($_->[0], $_->[1], undef, $_->[2]) }
     ATTRIBUTES);           # attribute formatters

最后我的对象打印例程如下:

BEGIN {
    foreach (ATTRIBUTES) {
    Class::_assign_gs_ai(__PACKAGE__, $_->[0], $_->[1]);
    }
}

有了继承,它看起来像这样:

my $self = [];

$#$self = I_LAST;
$self->[I_VERBOSITY] = $verbosity;
...

3 个答案:

答案 0 :(得分:1)

我不确定您的问题是什么,尽管我认为您花了很长时间说“我有一个期望B对象的函数,但我想将其传递给D对象。”

如果只希望使用某种确切类型的对象,请不要接受其他任何内容:

use Carp qw(croak);
sub Bf {
    croak "Bad object! I only like B" unless ref $_[0] eq 'B';
    ...
    }

但是,这是一个坏主意。派生类应与基类一样好。干净的解决方案是不在乎您得到哪种类型。

sub Bf {
    croak "Bad object! Doesn't respond to foo!" unless $_[0]->can('foo');
    ...
    }

由于此Bf方法适用于基类,所以为什么要在某些它不知道的派生类中查找某些东西呢?如果派生类更改了接口,并且不再像其父类那样工作,则可能不适合继承。诸如此类的许多问题可以通过不同的体系结构解决。

我认为您必须提出一个具体的示例,其中派生类不起作用。

答案 1 :(得分:1)

听起来由于某种原因,您需要D对象表现得像B对象,但同时不是就像D对象。正如现有答案和注释所表明的那样,在期望基类的地方使用子类是很常见的,并且大多数算法都不在乎您实际传递的是D还是B。我能想到的唯一原因就是D以不兼容的方式覆盖(重新定义)某些方法,而您却要使用B中的方法。

package Dog;
sub new {
  my ($class, %args) = @_;
  return bless \%args, $class;
}
sub bark { print "Bark!\n"; }

package Dingo;
use parent 'Dog';
sub bark { print "...\n"; }

package main;
my $dingo = Dingo->new;
$dingo->bark; # "..."

(注:为简洁起见,我省略了推荐的use strict;use warnings;,它们应在所有包装中使用)

通过阅读perldoc perlootutperldoc perlobj,您可能会意识到Perl中的对象仅仅是某种bless版本的引用;在上面的示例中,我们使用哈希引用。如果您试图获取仅存在于B中的“属性”,我认为您将不得不编写某种翻译方法。但是,如果您关心B中存在的方法,那么您要做的就是将它重新bless到父类中。

my $dingo = Dingo->new;
$dingo->bark; # "..."
bless $dingo, "Dog";
$dingo->bark; # "Bark!"

请注意,bless不会返回新引用,而是就地修改该引用;如果您希望它再次表现为Dingo,则必须将其bless退回。

也许更方便的是,您可以定义一个方法来为您创建一个副本并将其祝福到适当的类中:

package Dog;
sub as_dog {
  my ($self) = @_;

  # The {} below create a shallow copy, i.e., a new reference
  return bless { %{$self} }, __PACKAGE__;
}

#...
package main;
my $dingo = Dingo->new;
$dingo->bark; # ...
$dingo->as_dog->bark; # Bark!
$dingo->bark; # ...

答案 2 :(得分:0)

虽然似乎没有一个完美的解决方案,但临时“重新祝福”该物体似乎已经接近要求的条件:

sub Bf($) # expects a "B" object (or descendant of "B" (like "D"))
{
    my $B = shift;
    my $type = ref($B);  # save original type

    die "unexpected type $type" unless ($B->isa('B'));
    bless $B, 'B';       # restrict to "B"'s features
    $B->whatever(...);
    #...
    bless $B, $type;     # restore original type
}
相关问题