[Perl]类封装方法以及Class::Std的使用
众所周知Perl 5的面向对象并不是真正意义上的面向对象, 只是添加了一些函数使得它看起来像是面向对象而已。 通常bless出来的对象,它的属性可以被外部程序直接访问。 《Perl Hacks》的#43给出了一个方法,可以封装类属性, 使其不能直接被外部访问。另外 Class::Std 模块可以简单地实现这个方法。
通常的类都是这样声明的:
#
# My::Class::Original
#
# 最基本的声明类的途径。
# 在new函数中通过bless使$self成为类的引用。
#
# 这个途径生成的类的属性以散列值的形式存在于对象中,
# 因此对外界是完全可见的。
# 虽然本例中提供了getter/setter,
# 但使用者完全可以忽略getter/setter并直接访问属性值。
package My::Class::Original;
# 类的构造函数
sub new {
my ( $class, $data ) = @_;
#该类包含name和url两个属性
my $self = {
name => $data->{name},
url => $data->{url}
};
bless $self, $class;
return $self;
}
# name属性的getter
sub get_name {
my $self = shift;
return $self->{name};
}
# url属性的getter
sub get_url {
my $self = shift;
return $self->{url};
}
# name属性的setter
sub set_name {
my $self = shift;
$self->{name} = shift;
}
# url属性的setter
sub set_url {
my $self = shift;
$self->{url} = shift;
}
1;
通过 $s = My::Class::Original->new
生成对象之后,
就可以用 $s->{name}
直接访问它的属性。
稍稍用一些小技巧,即可将类属性隐藏在类之中:
#
# My::Class::InsideOut
#
# 通过闭包封装类的属性。
#
# 类属性并不以散列值的形式保存在类中,
# 而是保存在局部变量%names和%urls中,
# 再将局部变量的作用域限制在类之内,
# 就可以达到禁止外部直接访问类属性的目的。
#
# 通过这种途径定义的类,将只能通过getter/setter对类属性进行访问。
# 创建一个新的作用域,限制%names和%urls的有效范围
{
package My::Class::InsideOut;
use Scalar::Util 'refaddr';
# 类属性
my %names;
my %urls;
sub new {
my ( $class, $data ) = @_;
my $self = {};
bless $self, $class;
# 为类属性赋初始值
my $id = refaddr($self);
$names{$id} = $data->{name};
$urls{$id} = $data->{url};
return $self;
}
# 以下是类属性的getter/setter
sub get_name {
my $self = shift;
return $names{ refaddr($self) };
}
sub get_url {
my $self = shift;
return $urls{ refaddr($self) };
}
sub set_name {
my $self = shift;
$names{ refaddr($self) } = shift;
}
sub set_url {
my $self = shift;
$urls{ refaddr($self) } = shift;
}
}
1;
这样在试图直接访问类属性时,由于类中并不存在属性的散列值,因此会返回空字符串。
通过Class::Std模块, 只需书写很少的代码,即可实现上述手工封装的过程。
#
# My::Class::Std
#
# 使用Class::Std包来实现类属性的封装。
# 基本原理与My::Class::InsideOut相同,
# 只是由Class::Std包代替了手工编写代码的麻烦。
#
package My::Class::Std;
use Class::Std;
my %names : ATTR( :get<name> :set<name> :init_arg<name> );
my %urls : ATTR( :get<url> :set<url> :init_arg<url> );
1;