How can I make an object in my parent class but bless it into my child class in Perl?
我有两个类:基类foo::base和派生类Foo::Base::Sub。我想让Foo::Base::Sub对构造函数的参数(散列)进行一些类型和数据检查,然后再进行修改。我尝试重写Foo::Base->new的构造函数,进行检查,然后调用Foo::Base->new(因为代码完全相同):
1 2 3 4 5 6
| package Foo ::Base::Sub;
sub new {
...check argument 's type and data...
Foo::Base->new(%my_hash)
} |
问题是,通过调用Foo::Base的构造函数,散列现在将被视为foo::base对象而不是foo::base::sub对象。显而易见的解决方案是将来自Foo::Base::new的代码放入Foo::Base::Sub::new中,但随后我要重复代码。另一件事是foo::base不是我的——因此我希望避免在模块加载或不必要地分叉之后修改它。
在我看来,这个问题一定是以前出现的,所以一定有一个规范的解决方案。此外,它还涉及类型强制,这通常不是Perl的问题。
那么,有没有简单的修改,或者我这样做是错误的?
- 对于类型强制,如果您还没有检查驼鹿,您可能需要检查。你可以在对象和属性构造中构建很多"自动"的东西。
- ive actually used Moose before but Im真的试图理解Perls guts a bit better. In the past I避开了OOP模型,而倾向于命令式和伪函数式,所以这类事情从来没有出现过。
- 我们将在中间Perl中介绍这种情况:)
标准的Perl习惯用法是使用SUPER调用继承链:
1 2 3 4 5 6 7 8 9 10 11
| @Foo::Base::Sub::ISA = qw(Foo ::Base);
sub new {
my $package = shift;
my $self = $package->SUPER::new();
# Other subconstructor stuff here
return $self;
} |
如注释所述,Foo::Base的构造函数必须使用bless的两种参数形式:
1 2 3 4 5 6 7 8 9
| sub new {
my $package = shift;
my $self = bless {}, $package;
# Other superconstructor stuff here
return $self;
} |
当调用超类的构造函数时,$package将是子类。
- 注意,只有在超类构造函数使用bless的两个参数形式显式设置新对象的包名称时,这才有效。(这就是为什么你应该一直使用bless $self, $class)
- 另一种说法是foo::base的构造函数被破坏或者没有。
- 使用"parent"pragma而不是直接操作@isa。
- 酷。我只使用super来保证调用了正确的超类方法。看到你的答案促使我更仔细地看一下Perlobj,现在一切都有意义了。谢谢。
- @埃瑟:谢谢你的提醒。
- @Ether=>每个人都对use parent或use base说,但背后的理由是什么?我无法想象@is a的用法会发生变化,our @ISA = qw/Foo::Base/看起来并不那么复杂,它会准确地告诉你发生了什么,如果需要的话,它会明确地抱怨不需要基类。
- @埃里克·斯特罗姆:有多种原因;如果您在运行时而不是编译时设置@isa(它开始于我们的@isa),您可能会遇到一些奇怪的编译问题。}也可以解决)并且可以方便地强制加载子类的模块,而不是(意外或故意)依赖其他模块来加载它们。Base还处理使用fields.pm的类的子类(一个罕见的,我希望得到更罕见的野兽)
- @埃里克·斯特罗姆:不,它不会总是抱怨:在这里,使用foo::bar::baz会自动创建一个空的foo::bar包,因此不会得到忽略use Foo::Bar的警告。
- 另一个需要注意的警告是:$package->SUPER::new实际上调用了当前包的超类(即,由最新的package声明指定的,或者如果没有main声明指定的),而不是$package。这一差异通常可以忽略,因为$package要么与uuu包uuu的子包相同,要么是uuu包uuuuu的子包,但如果您正在执行一些奇怪的多态性,您可能会看到需要一段时间才能调试的结果。:)
- @ysth=>good information,您有没有任何涉及编译顺序问题细节的参考资料?自从我发现我的大部分子包都小到可以内联以来,我一直在避免使用基包和父包,同样的文件语法有点笨拙。
- @埃里克·斯特罗姆:不,我不这样认为;它不时出现在Perlmonks上,人们在这里进行递归使用,或者在模块中使用主线代码,试图调用那些最终还不在继承树中的方法。我很难从记忆中找出一个简短的例子。我喜欢父级的相同文件语法。
我习惯于把这个分成两部分,new和init。
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
| package Foo ::Base;
sub new {
my $class = shift;
my $self = bless {}, $class;
return $self->init(@_);
}
sub init {
my ($self, @params) = @_;
# do something initialization and checks
return $self;
}
package Foo ::Sub;
use base 'Foo::Base';
sub init {
my ($self, @params) = @_;
# do something initialization and checks
$self = $self->SUPER::init(@params);
# do something other if you wish
return $self;
} |
注意,'Foo::Sub'不实现new构造函数。
- 如果init()失败,并且希望整个构造失败,那么两阶段的构造就不太理想了——最终会得到一个部分构造的对象处于未知状态,可能无法使用。
- 我从教育学的角度喜欢这个答案,但我不愿意把结构分为两部分。尤其是当调用class->new(…)是如此标准的时候。谢谢你的回答。
- 当init失败时,new失败,因为没有eval和init在new内部被调用。这就是全部。
- 当init()失败时,只返回Nothing,这与new()失败时会发生的情况相同。
您可能希望了解调用super的各种方法。超级模块可能会工作,尽管我自己没有尝试过。
- 这是一个很好的模块,尽管它的文档已经过时了。然而,阅读它的来源是很有教育意义的。
- 有趣。今晚我要检查一下资料来源。
虽然"超级"答案确实描述了您在这里想要什么,但是您可以(在更一般的情况下)通过这样的操作调用任意包中的方法代码:
1 2
| $object = Foo::Class->new(...);
$object->Bar::Class::method(@args); |
Bar::Class::方法将正常进行,如下所示:
1 2 3
| package Bar ::Class;
sub method {
my ($self, @args) = @_; |
只有$self和平常不同。当然,做这个而不是做很多事情可能是你做错了什么的一个信号。
还可以比较next和perl 5.10的"mro"。