在 Perl 中,通常不会通过语言的语义来隐藏字段,而是通过文档形式的合同来隐藏字段。但是,可以通过使用闭包来隐藏字段。还值得注意的是,Perl 并没有在语义上区分类方法和实例方法。
实现对象的标准方法之一是祝福哈希,就像你一样。此哈希包含所有实例变量/字段。习惯上用下划线开始“私有”字段。通常,合约(文档)不会说明这些字段是如何存储的,但会要求类的用户通过各种方法调用。
类变量不应与实例一起存储。最好使用全局变量或词法变量。在您提供的代码中,$count
它只是一个计数器,但您永远不会将它作为类变量访问。相反,您为每个实例分配一个唯一 ID。要将它用作类变量,请提供适当的访问器(我去掉了像return
s 这样的不必要的东西):
{
package Base;
my $count = 0;
sub new {
my ($class) = @_;
my $self = {
ID => $count++,
};
bless $self, $class;
}
sub Count { $count }
sub ID { my ($self) = @_; $self->{ID} }
sub report { my ($self) = @_; "I am the Base object ".($self->ID)."." }
}
=head1 Base
A generic base class
=head2 Base->Count
Return the object count.
=head2 $base->ID
Give the unique ID of this object.
=head2 $base->report
Returns a string containing a short description.
=cut
子类与计数无关。这是由上述变量的范围强制执行的$count
,通过外部花括号表示。subs 是这个变量的闭包。
{
package Sub;
use parent -norequire, qw(Base); # remove `-norequire` if Base in different file
sub new {
my ($class) = @_;
my $self = $class->SUPER::new;
$self->{Name} = undef;
$self;
}
sub Name :lvalue {
my ($self) = @_;
$self->{Name};
}
sub report {
my ($self) = @_;
"I am the Sub object ".($self->ID)." called ".($self->Name).".";
}
}
=head1 Sub
A generic subclass. It subclasses Base.
=head2 $sub->Name [= SCALAR]
Gets or sets the name of $sub.
my $oldname = $sub->Name;
$sub->name = "new name";
=cut
如您所见,Sub
构造函数调用Base
初始化程序,然后添加一个新字段。它没有类方法或类变量。该类无法访问该$count
变量,除非通过访问器类方法。合同通过 POD 文档说明。
(在该Name
方法中,我使用了:lvalue
注解。这使我可以简单地分配给对象中的适当字段。但是,这不允许检查参数。)
测试用例
my $base1 = Base->new; my $base2 = Base->new;
print "There are now " . Base->Count . " Base objects\n";
my $sub1 = Sub->new; my $sub2 = Sub->new;
print "There are now " . Base->Count . " Base objects\n";
$sub2->Name = "Fred";
print $_->report . "\n" for ($base1, $sub1, $base2, $sub2);
印刷
There are now 2 Base objects
There are now 4 Base objects
I am the Base object 0.
I am the Sub object 2 called .
I am the Base object 1.
I am the Sub object 3 called Fred.
漂亮,不是吗?(除了$sub1
,那个对象没有它的名字。)
可以使用 来查看文档perldoc -F FILENAME
,并会输出类似
Base
A generic base class
Base->Count
Return the object count.
$base->ID
Give the unique ID of this object.
$base->report
Returns a string containing a short description.
Sub
A generic subclass. It subclasses Base.
$sub->Name [= SCALAR]
Gets or sets the name of $sub.
my $oldname = $sub->Name;
$sub->name = "new name";
只有在 *nix 系统上才能更好地排版。
在 v5.12.4 下测试。
编辑:由内而外的对象
虽然由内而外的对象提供了更好的封装,但它们并不是一个好主意:难以理解、难以调试和难以继承,它们提供的问题多于解决方案。
{
package Base;
my $count = 0;
sub new { bless \do{my $o = $count++}, shift }
sub Count { $count }
sub ID { ${+shift} }
sub report { my ($self) = @_; "I am the Base object ".($self->ID)."." }
}
{
package Sub;
my @_obj = ();
my $count = 0;
sub new {
my ($class) = @_;
$count++;
$_obj[$count - 1] = +{
parent => Base->new(),
Name => undef,
};
bless \do{my $o = $count - 1}, shift;
}
sub Name :lvalue { $_obj[${+shift}]{Name} }
sub AUTOLOAD {
my $self = shift;
my $package = __PACKAGE__ . "::";
(my $meth = $AUTOLOAD) =~ s/^$package//;
$_obj[$$self]{parent}->$meth(@_)
}
sub report {
my ($self) = @_;
"I am the Sub object ".($self->ID)." called ".($self->Name).".";
}
}
此实现具有完全相同的接口,并以相同的输出完成测试用例。这个解决方案远非最佳,只支持单继承,做一些中间的事情(自动加载,动态方法调用),但它确实工作得令人惊讶。每个对象实际上只是对 ID 的引用,可用于查找包含字段的实际散列。无法从外部访问保存散列的数组。该类Base
没有字段,因此不必创建对象数组。
Edit2:对象作为代码引用
另一个坏主意,但编写代码很有趣:
{
package Base;
my $count = 0;
sub new {
my ($class) = @_;
my $id = $count++;
bless sub {
my ($field) = @_;
die "Undefined field name" unless defined $field;
if ($field eq "ID") { return $id }
else { die "Unrecognised name $field" }
}, $class;
}
sub Count { $count }
sub ID { my ($self) = @_; $self->("ID") }
sub report { my ($self) = @_; "I am the Base object " . $self->ID . "." }
}
{
package Sub;
use parent -norequire, qw(Base);
sub new {
my ($class) = @_;
my $name = undef;
my $super = $class->SUPER::new;
bless sub {
my ($field, $val ) = @_;
die "Undefined field name" unless defined $field;
if ($field eq "Name") { defined $val ? $name = $val : $name }
else { $super->(@_) }
}, $class;
}
sub Name { my $self = shift; $self->("Name", @_) }
sub report {
my ($self) = @_;
"I am the Sub object ".($self->ID)." called ".($self->Name).".";
}
}
测试用例必须适应$sub2->Name("Fred")
,并相应更新文档,因为我们不能在这里安全地使用左值注释。