当我想将此机制应用于派生类的Perl对象时,我意识到Perl不是静态类型的:
说我有一个基类B
和一个派生类D
继承自B
。
另外,我还有一个对象$obj
,它保存着一个D
对象。
函数Bf()
期望的参数类型为B
。
显然(根据多态性规则),我可以像$obj
一样将Bf()
传递给Bf($obj)
,但是与静态类型的语言Bf()
不同的是,整个{ {1}}对象(而不仅仅是D
的元素)。
在Perl中是否有(相当干净和简单)的解决方案?解决方案应“隐藏” B
中B
中D
不具有的属性(和方法),而不是限制对原始Bf()
(即{{ 1}}实际上。
好的,人们想要更具体的描述。 不幸的是(如前所述),原始程序非常复杂,并使用类似反射的机制自动生成getter,setter和formatter,我真的不能在此给出一个最低限度的示例,因为它会不小。
首先,我有一个处理消息的类B
(毫不奇怪!)。
然后,我有一个函数D
,该函数期望MessageHandler
对象作为第一个参数。
然后我有这个类的层次结构(实际上要复杂得多):
log_message($$$)
现在,如果MessageHandler
想要MessageHandler
ControlMessageHandler (ISA: MessageHandler)
ControlMessageResponseHandler (ISA: ControlMessageHandler)
,我可以通过log_message
,因为它符合MessageHandler
。
但是这样做会将ControlMessageResponseHandler
中不存在的MessageHandler
到ControlMessageResponseHandler
的所有属性都暴露了。
危险是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;
...
答案 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 perlootut
和perldoc 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
}