我对perl很新,而且我遇到了家庭作业问题。我有一个带有类变量的对象,它计算创建的实例数。然后我有一个带有实例变量的子类。
我的第一个问题是,如何让用户隐藏类变量?我尝试使用闭包但无法弄清楚如何使继承工作。事实上,它是一个类变量使它变得更糟,因为增加它的代码执行了两次,它说我有两个实例,当我有一个。不完全确定它为什么会发生,但它是有道理的。我尝试使用标量,但变量再次没有正确递增。还没有尝试过“内外对象”而且我不确定我是否愿意,这似乎超出了我的想象。我觉得封装类变量与封装实例变量不同,但我找不到任何解释如何操作的东西。
我的第二个问题是,正如我所提到的,我无法使用继承来进行封装。当您从子类调用超级构造函数时,使用闭包,您将获得对子例程权限的引用,因此无法(我知道)将实例变量添加到该子句中。
这是我的基类:
#!/usr/bin/perl -w
use strict;
package Base;
my $count = 1;
sub new {
my $class = shift;
my $self = {
_Count => $count # not hidden
};
$count++; # increment count
bless $self, $class;
return $self;
}
sub Count { # getter
my $self = shift;
return $self->{_Count};
}
1;
这是我的子类:
#!/usr/bin/perl -w
use strict;
package Sub;
use Base;
our @ISA = qw(Base);
sub new {
my $class = shift;
my $self = $class->SUPER::New();
$self->{_Name} = undef; # not hidden
return $self;
}
sub Name { #getter/setter
my($self, $name) = @_;
$self->{_Name} = $name if defined($name);
return $self->{_Name};
}
1;
答案 0 :(得分:4)
如果您使用的是裸Perl 5(而不是使用OO框架),那么执行类变量的常用方法是只对访问者可见的词汇:
{
my $count = 0;
sub Count {
my ($self, $new_count) = @_;
if (defined $new_count) { # NB only works if undef is not a legit value
$count = $new_count;
}
return $count;
}
}
$count
仅在封闭区块中可见;甚至在同一个类上的其他方法也看不到它。但任何人都可以使用$base_obj->Count
或Base->Count
来操纵它,任何此类操作都会影响共享变量。
您还可以使用闭包来提供真正隐藏的实例变量。除非你正在完成家庭作业的任意规则,否则这不值得做。
package Base;
sub new {
my ($class, $name) = @_;
die "Need name!" unless defined $name;
my $age;
return bless sub {
my ($attribute, @args) = @_;
if ($attribute eq 'name') {
if (@args) {
die "Attempt to set read-only attribute!";
}
return $name;
}
if ($attribute eq 'age') {
if (@args) {
($age) = @args;
}
return $age;
}
die "Unknown attribute $attribute";
} => $class;
}
sub name {
my ($self, @args) = @_;
return $self->(name => @args);
}
sub age {
my ($self, @args) = @_;
return $self->(age => @args);
}
这里发生的事情是new
返回的受祝福的子关闭了两个词法$name
和$age
。当new
返回时,那些词法会超出范围,从那一点开始访问它们的唯一方法是通过关闭。闭包可以检查其参数以允许或拒绝访问它所拥有的值。只要它永远不会返回引用,就可以确保它只能直接访问这些变量。
这也适用于继承,没有太多额外的细微之处:
package Derived;
use base 'Base';
sub new {
my ($class, $name, $color) = @_;
my $base_instance = $class->SUPER::new($name);
return bless sub {
my ($attribute, @args) = @_;
if ($attribute eq 'color') {
if (@args) {
($color) = @args;
}
return $color;
}
# base class handles anything we don't, possibly by dying
return $base_instance->($attribute, @args);
} => $class;
}
这模拟了基本和派生类实例数据具有不同存储的语言,在本地处理请求或将其传递给已添加到闭包的基类实例。更深的继承树将导致闭包闭合闭包的闭包,每个闭包都可以选择关闭该特定类所需的实例变量。
这是一个相当大的混乱,很难检查和调试,这就是为什么我要再强调一次你永远不应该这样做。但是理解它是非常有用的,为此我将你引用到SICP。
答案 1 :(得分:3)
作为模块本地my
变量,$count
已经从模块/类的用户隐藏。看起来好像您正在使用实例变量_Count
作为“当前ID”类型变量,因此创建的每个对象(实例)都会从1开始获取一个新ID。(相反,它是为了跟踪数字在活动实例中,您需要在DESTROY
中递减它,并且不需要在对象中存储副本。)如果您的测试代码只创建一个实例,那么它的Count()
方法应该返回1但是$count
将为2,因为它从1开始并在将旧值存储在对象中后递增。
在perl中,通常会在$self
哈希中存储实例变量,而不会隐藏它们,尽管有时会使用前缀来避免冲突。它们受到约定的保护(依赖于实现细节是不安全的,因为它们可能会更改)而不是语言功能。
如果您想要对perl类进行更高级别的控制,请查看Moose模块套件。
答案 2 :(得分:3)
在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
类没有字段,因此不必创建任何对象数组。
又一个坏主意,但代码很有趣:
{
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")
,文档也相应更新,因为我们不能安全地使用左值注释。
答案 3 :(得分:3)
引用perldoc perlmodlib
," Perl不强制执行其模块的私有和公共部分,因为您可能习惯于其他语言,如C ++,Ada或Modula-17。 Perl并没有对强制隐私感到迷恋。因为你没被邀请,所以你宁愿离开它的起居室,也不是因为它有霰弹枪。"
Perl中的标准约定是将所有内容放入$self
哈希并使用下划线前缀来指示哪些项应被视为私有...然后信任该类的用户以尊重该指示。同样的惯例也适用于方法。如果您使用我的某个模块并且您选择在封面下查看并直接修改$self
的内容或致电$obj->_some_private_method
,那么您将进入树林并可能会破坏某些内容,或者在升级到下一个版本时,此版本中的工作正常可能会中断;如果发生这种情况,你可以保留两件。
如果您要坚持让课程本身以外的任何人无法访问数据,那么有办法实现这一点,但是a)它们增加了复杂性,几乎在所有情况下都是不必要的,并且b)因为你和已经看到,他们倾向于使继承变得更加麻烦。
那么,我的问题是,你实际上是想要实现什么,为什么你觉得需要制作你的对象数据Sooper-Sekret并且完全无法访问?通过简单地标记您认为应该被视为私有的东西,然后信任他人让他们独自(除非他们有充分理由不这样做),您将获得什么好处?
答案 4 :(得分:2)
首先,我不确定“隐藏于用户”的确切含义,但看起来您可能正在寻找包范围变量(our)与实例作用域。
package MyBaseClass;
use warnings;
use strict;
our $counter = 0;
sub new {
my $class = shift;
$counter++;
return bless {}, $class;
}
sub howManyInstances {
return $counter;
}
1;
关于你的第二个问题,我不确定什么闭包与继承有关。 这是一个简单的子类:
package MySubClass;
use warnings;
use strict;
use parent 'MyBaseClass'; # use parent schema, don't mess with @ISA
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{_name} = undef;
return $self;
}
# Your setter/getter looks ok as is, though lowercase is tradional for methods/subs
1;