基于数组的方法基于匿名哈希表的面向对象编程方法中有两个明显的不足:一是无法为属性提供一种访问限制,限制外部对内部属性的访问和改变。二是在处理大规模的实例的情况下,系统的内存开销颇大。 100 个实例意味着将创建 100 个散列表,这 100 个散列表都要为插入新纪录的操作而分配额外的存储空间。除了基于匿名散列表的实现,我们也可以利用数组来存储属性,实现面向对象的编程。
整个实现的数据结构非常简单,我们将为每一个类的实例属性分配一个数组(见图一,图中的每一列对应于类的一个实例属性),而每一个新的实例将是跨越所有数组列的一个切片(图中的每一个被使用的行对应于类的一个实例)。每次需要实例化一个新的对象,new 函数将被调用。一个新的逻辑行将被分配,新的实例的实例属性将以新的行偏移量插入到相应的属性列当中去。
图 1. 基于数组方法的面向对象编程实现虽然在 CPAN 上有许多基于这一方法的实现,为了更加清楚地说明如何实现基于数组存储属性的面向对象编程,我们自己动手实现了一个简单的实例。我们定义了一个 InsideOut 类(模块),所有的需要使用基于数组存储属性的面向对象编程的类必须继承这个类。 InsideOut 通过为每个包维护一个称做为 @_free 的“空余行列表”来重用那些被定义之后又被释放的行(空余行)。通过精心设计的数据结构,这个列表成为了一个包含所有空余行信息的链表,并且通过一个名为 $_free 的变量变量指向链表的头部。表中的每个元素包含了下一个空余行的索引。当一个对象的实例被删除时,$_free 将指向这个被释放的行,而空余列表中相应的这个行中的元素将含有指向原有 $_free 所指向的前一个条目。因为被释放的“所谓”空余行和被使用的行不会重叠,所以我们可以自己的使用其中的一个属性列来保存 @_free 。这是通过 typelogb 别名机制来实现的。
我们设计的 InsideOut 模块为一个继承它的类提供如下的功能:
一个名为 new 的构造函数,负责将为 bless 到继承类中的对象分配空间。 new 函数将会自动地调用 initialize,而 initialize 可以在继承它的类中被重载,进行用户自己定义的初始化工作。
我们将定义一组访问函数,用于存取属性。这是一组已 get_attribute 和 set_attribute 为名称的方法,将在继承类被自动创建,包括对象自己的方法,任何人只能通过这些方法来存取对象属性。由于 InsideOut 模块是唯一知道如何存取属性的模块,所以用户无法通过除此之外的任何方法来存取对象的实例属性。
一个名为 DESTROY 的析构函数。
InsideOut 模块的具体实现如下,见清单 7 到清单 11 。例七部分包含了 InsideOut 模块的对外接口函数。继承 InsideOut 模块的类通过调用它提供的 define_attributes 函数,自动生成自己类的构造函数和实例属性访问函数。
清单 7. InsideOut 模块的对外接口函数 define_attributes1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| package InsideOut;
require Exporter;
@InsideOut::ISA = qw (Exporter);
@InsideOut::EXPORT = qw (define_attributes);
sub define_attributes {
my $package = caller;
@{"${package}::_ATTRIBUTES_"} = @_;
my $code = "";
foreach my $attribute ( get_attribute_names($package) ) {
@{"${package}::_$attribute"} = ();
unless ( $package->can("get_${attribute}") ) {
$code = $code . _define_get_accessor ($package, $attribute);
}
unless ( $package->can("set_${attribute}") ) {
$code = $code . _define_set_accessor ($package, $attribute);
}
}
$code .= _define_constructor ($package);
eval $code;
if ($@) {
print $code . "\n";
die "ERROR: Unable to define constructor and accessor for $package \n" ;
}
}
|
清单 8 定义了内部函数 _define_get_accessor 和 _define_set_accessor,分别负责自动生成实例属性的存取方法。清单 9 定义了内部函数 _define_constructor,这个函数负责生成继承与 InsideOut 模块的类的构造函数 new () 。例十是一个由 InsideOut 模块自动生成的代码的清单。
清单 8. 负责自动生成存取实例属性方法的代码片断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
26
27
| sub _define_get_accessor {
my ($package, $attribute) = @_;
my $code = qq {
package $package;
sub get_${attribute} {
return \$_${attribute}\[\${\$_[0]}]
}
if ( !defined ( \$_free ) ) {
\*_free = \*_$attribute;
\$_free = 0;
}
};
return $code;
}
sub _define_set_accessor {
my ($package, $attribute) = @_;
my $code = qq {
package $package;
sub set_${attribute} {
if ( scalar (\@_) > 1 ) {
\$_${attribute}\[\${\$_[0]}] = \$_[1];
}
}
};
return $code;
}
|
清单 9. 自动生成构造函数的代码片断1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| sub _define_constructor {
my $package = shift;
my $code = qq {
package $package;
sub new {
my \$class = shift;
my \$id;
if ( defined (\$_free[\$_free]) ) {
\$id = \$_free;
\$_free = \$_free[\$_free];
undef \$_free[\$_id];
} else {
\$id = \$_free++;
}
my \$object = bless \\\$id, \$class;
if ( \@_ ) {
\$object->set_attributes (\@_)
}
\$object->initialize();
return \$object;
}
};
return $code;
}
|
我们继承 InsideOut 模块并且定义一个名为 People 的对象,如清单 10 所示。看看 InsideOut 模块如何为我们自动生成实例属性访问函数和 People 对象的构造函数 new () 。
|