你好Perl社区的SO。我使用Perl已有几年了,但是因为我遵循了这个,所以我认识到Perl还不够。
在过去的4年里,我写了一篇非常大的剧本,试图用OO风格来做这件事。我知道Perl< 6并不是真的OO。
所以我不喜欢的一点是我没有数据封装,这意味着没有对包(“类”)真正私有的变量(或者我可能不知道如何去做)。 / p>
我有类似的东西(只是我脚本的一小部分)
package TAG;
sub new () {
my $classname = shift;
my $self = {};
bless( $self, $classname );
$self->initialize();
return $self;
}
sub initialize() {
my $self = shift;
# Only an example, I have a long list of items in this "class"
$self->{ID} = "NA";
}
sub setID() {
...
}
sub getID() {
...
}
在我的主脚本中,我正以这种方式使用它:
my $CurrentItem;
$CurrentItem = new TAG();
$CurrentItem->getID()
但是
$CurrentItem->{ID} = "Something";
也在运作,但我更愿意,这是不可能的。
有没有办法更好地封装我在“类”中使用的数据,以便我(或其他用户)被迫使用get和set方法?
答案 0 :(得分:12)
这是一个已在多个地方讨论过的问题,有几种可能的解决方法,但它们都不一定是理想的。
This paper使用Tie :: SecureHash讨论闭包,标量和有限访问哈希等选项,而不是最后一种方法。
This blog认为在perl中有时候应该违反封装,尽管这些评论会带来一些负面影响。
您还可以查看moose的Perl 5对象。它的目的是鼓励使用encapsulated objects。
答案 1 :(得分:6)
Perl支持封装的部分:委托和信息隐藏。封装的另一部分就是我称之为“行为有限数据”,将数据与行为相关联。在维基百科关于“Encapsulation (object-oriented programming)”的文章中,它暗示“封装用于指代两个相关的中的一个,但不同的概念”(italics mine)。列出的第二个是
- 一种语言结构,有助于将数据与对该数据进行操作的方法(或其他功能)捆绑在一起。
文章的很大一部分是“信息隐藏”。 Perl允许隐藏复杂性的OO类型,只要你看起来不太难。我在Perl中一直使用封装 。我解决了很多问题并且一次又一次地使用,并认为只要接口类没有“到达”,行为就应该如预期的那样。
大多数动态语言通过 manners 实现了很多功能,而较重的语言通过安全封装实现了这些功能。但是,Perl允许您为任何类型的引用分配行为,并且Inside-Out objects在Perl中可能与任何其他形式的封装一样安全 - 尽管更多的是一件苦差事写,但这只是给你一个案例,你选择一个安全到详细的权衡为需要它的类。
答案 2 :(得分:5)
您可以尝试Moose(或Mouse或Any::Moose)。
package TAG;
use Moose;
has ID => (
reader => 'getID', # this is the only one that is needed
writer => 'setID',
predicate => 'hasID',
clearer => 'clearID',
isa => 'Str', # this makes setID smarter
required => 1, # forces it to be included in new
);
has name => (
is => 'ro', # same as reader => 'name',
required => 1, # forces it to be included in new
);
# Notice that you don't have to make your own constructor.
# In fact, if you did, you would effectively break Moose
# so don't do that.
虽然这实际上并没有阻止TAG->{ID}
访问,但它会为你做几件事。
大致相当于:
package TAG;
use strict;
use warnings;
sub getID{
my($self) = @_;
return $self->{ID};
}
sub setID{
my($self,$new_id) = @_;
# die if $new_id is anything other than a string
# ( the one produced by Moose is smarter )
die if ref $new_id;
die unless defined $new_id;
$self->{ID} = $new_id
}
sub hasID{
my($self) = @_;
return exists $self->{ID}
}
sub clearID{
my($self) = @_;
delete $self->{ID}
}
sub name{
my($self) = @_;
return $self->{name}
}
# The constructor provided by Moose is vastly superior
# to the one shown here.
sub new{
my($class,%opt) = @_;
my $self = bless {}, $class;
die unless exists $opt{ID}; # this was the required => 1, from above
$self->setID($opt{ID});
die unless exists $opt{name}; # this was the required => 1, from above
$self->{name} = $opt{name};
}
有一种理论认为,给定代码段中的错误的数量与代码的行的数量成正比。
如果那是真的,那么我会说Moose版本的错误明显减少。
实际上,如果我已经实现了Moose为你做的所有事情,那么代码行的数量将是现在的两倍多。
如果你真的需要来阻止$TAG->{ID}
访问,你可以使用另一个元类,同时仍然使用Moose。 不要问我怎么做,我最近才开始使用Moose。 子>
答案 3 :(得分:2)
perl中最直接的解决方案就是将该变量加上前缀(即“_ID”,“private_ID”或任何你想要的东西),然后不记录,因为它不是接口的一部分。
还有其他方法可以做到,但是你必须要问谁会试图像这样打破它,如果他们想要滥用你的代码那么你无论如何都会遇到麻烦。
答案 4 :(得分:2)
如果你想要封装,为什么要在构造函数中破解它?而不是直接重置$self->{ID}
,您应该使用setID
方法进行设置。
你可以做各种各样的时髦东西,以防止访问和修改你的对象内部。
最简单的方法之一是使用lock_hash锁定对象。这样,任何修改其内部的尝试都会造成致命的错误。
您也可以使用由内而外的物体。
您可以重载哈希访问以防止访问子类外部:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my $f = Foo->new();
$f->bar(15);
print Dumper $f;
eval {
$f->{bar} = 23;
1;
} or do {
print "Direct access error: $@";
};
print Dumper $f;
BEGIN {
package Foo;
use strict;
use warnings;
use overload '%{}' => '_HASH_DEREF';
sub new {
return bless {};
}
sub bar {
my $self = shift;
$self->{bar} = shift if @_;
return $self->{bar};
}
sub _HASH_DEREF {
my $caller = caller;
die "Illegal access to object internals"
unless $caller eq __PACKAGE__;
return shift;
}
}
您可以祝福使用方法名称调用的子例程,以生成用作S3存储桶的魔术秘密值。
你可以想到隐藏数据的任何奇怪的事情。
Perl对象让你可以在后端做任何你想做的事情来处理存储和/或封装。
如果你有一个欺骗并直接访问对象内部的团队,那么你就有一个社会问题需要解决。技术修复 MAY 帮助。真正的解决方法是改变开发团队的行为。