第七章 对象(二)-默认对象系统

神圣引用

Perl默认的面向对象系统真心小巧,只有3条规则:

  • 一个类就是一个包
  • 一个方法就是一个函数
  • 一个引用(blessed,赐福)就是一个对象(我把bless过的引用翻译成神圣引用,所以一个神圣引用就是一个对象

由这3条规则,你可以造出任何东西。但仅仅靠这些来构建一个大型项目显然太过简约,特别它是缺乏元编程高度抽象的能力。对于超过了几百行的(现代)程序,使用Moose是更好的选择,然而还有大量的遗留代码仍然使用的是默认的面向对象系统。

前2条规则,我们已经在之前已经介绍过了。内置函数bless的作用就是将类名和引用联系起来,之后该引用就成为一个有效的调用者,Perl就能在该引用上进行方法的调度。(通过引用联系到类,调度类中的方法)

构造函数就是创建对象的方法(类方法)。按惯例,构造函数的名字是new(),但这个不是强制的。

bless有2个操作数,一个引用和一个类名。引用可以是任意有效的引用,空引用也行;类名是可选项,默认为当前包名;bless的返回值就是bless过的引用(神圣引用)。一个简单构造函数:

sub new
{
my $class = shift;
bless {}, $class;
}

这个构造函数将调用者取出来作为类名。你也可以硬编码类名,但那样就不灵活了。带参数的构造函数在继承、委托或导出的时候能重用。

通过引用的类型就能了解对象实例是如何存储自己数据的。哈希引用最为常见,但是其他类型的引用也是可以bless成对象的:

my $array_obj = bless [], $class;
my $scalar_obj = bless \$scalar, $class;
my $func_obj = bless \&some_func, $class;

Moose的类定义需要进行属性的声明,但Perl的默认OO系统要求没那么严。一个表示篮球运动员的类,存储球衣号码和位置,它的构造函数可能是这样的:

package Player
{
sub new
{
my ($class, %attrs) = @_;
bless \%attrs, $class;
}
}

创建新球员就是这样的:

my $joel = Player->new( number => 10, position => 'center' );
my $damian = Player->new( number => 0, position => 'guard' );

它的类方法可以像访问哈希元素一样直接访问对象属性:

sub format
{
my $self = shift;
return '#' . $self->{number} . ' plays ' . $self->{position};
}

这样有个问题就是:修改对象的内部数据可能会破坏其他的代码,相比而言使用访问器的方式就更加安全。

sub number { return shift->{number} }
sub position { return shift->{position} }

现在你不得不自己动手编写那些Moose免费为你提供的功能了。Moose鼓励人们使用访问器、设置器,而不是直接操作对象属性。

方法调度和继承

给定一个神圣引用$joel,这样调用其方法:

my $number = $joel->number;

首先找到类(类跟引用联系起来了)$joel,在这里是Player类。下一步Perl会在Player里面去找一个叫number()的函数。如果函数不存在,并且Player继承了一个父类,那么Perl就去父类里面找,再往父类的父类里找,依次类推,直到找到number(),只要找到(任何地方),就以$joel 为调用者调用该函数。

CPAN上有个模块namespace::autoclean可以帮助你避免因导入函数而引起的名字冲突。

Moose提供了extends来跟踪继承关系,Perl则使用包全局变量@ISA来跟踪。方法调度就是在每一个类的@ISA里面去找父类。如果InjuredPlayer 类继承 Player类,你可以这样写:

package InjuredPlayer
{
@InjuredPlayer::ISA = 'Player';
}

或使用编译指令parent,写法会更简单:

package InjuredPlayer
{
use parent 'Player';
}

Moose因为有自己的元模型存储继承信息,所以会拥有更多的元编程机会。

你可以继承多个父类:

package InjuredPlayer
{
use parent qw( Player Hospital::Patient );
}

AUTOLOAD

如果一直没有找到要调用的方法,那么Perl就会转而去寻找AUTOLOAD()方法(之前讲过的)。你可能意识到了,某些情况下问题会变得复杂。在多重继承中它到底会调用哪个AUTOLOAD()方法呢?

重写方法

默认OO支持重写方法,但它没有提供机制来让你表明:****你要重写父类方法****。导致的结果就是,任何你定义、声明、或导入到子类的函数都会重写父类中的同名方法!

要重写方法,声明一个同名的方法即可,在重写的方法内部,可以用SUPER::来调用父类方法:

sub overridden
{
my $self = shift;
warn 'Called overridden() in child!';
return $self->SUPER::overridden( @_ );
}

SUPER::前缀就是告诉方法调度器去父类调用。你可以提供自定义参数,但一般是@_,记得要将调用者卸载掉啊(@_的第一个参数)。

SUPER::有个不好的特性就是,如果你从其他包导入方法,Perl有可能找不到正确的父类。 因为兼容性,这个特性一直保留着。CPAN上的SUPER模块提供了一种解决方法。
Moose没有这样问题。

应对神圣引用的策略

默认OO系统(神圣引用)小巧但混乱,相比而言Mosse更容易使用,所以应该尽可能的选择Moose。如果你必须要维护一些使用神圣引用的代码,或者你还没能说服你的团队整体迁移到Moose上来,这里有些建议,可以帮助你避免一些坑:

  • 在同一个类中不要混合函数和方法
  • 尽量每个类使用一个.pm文件
  • 遵循默认OO系统的标准,比如构造函数是new(),$self就是调用者的名字
  • 使用访问器,即使是在方法中。 Class::Accessor这个模块可能对你有用
  • 避免使用AUTOLOAD()
  • 要考虑别人或者别的地方会使用你的类,bless时使用2个参数,将类拆成最小的行为单元。
  • 使用模块来帮助你重用代码,如 Role::Tiny

你可能感兴趣的:(第七章 对象(二)-默认对象系统)