Perl 5 的默认对象系统故意最小化。以下三条简单的规则相组合构成了简单────但有效的 ────基本对象系统:
一个类就一个包;
一个方法就是一个函数;
一个(bless 后的)引用就是一个对象。
你已经在 Moose 里见过了前两条规则。第三条规则是新出现的。bless
关键字将一个类 的名称和一个引用关联起来,使得任何在该引用上进行的方法调用由与之相关联的类来解析。 它听上去比实际的要复杂一些。
默认的 Perl 5 对象构造器是一个创建引用并对其进行 bless 的方法。出于惯例,构造器通 常命名为 new()
,但并非一定如此。构造器几乎总是 类方法:
bless
接受两个参数,与类相关联的引用以及类的名称。虽然程序抽象建议使用一个单 独的方法来处理,但你仍可以在构造器或类之外使用 bless
。类名称不需要事先存在。
设计上,构造器以被调用者的形式接收类名。直接硬编码类名称也是可能的,但不推荐。 参数式的构造器使得此方法可以通过继承、委托或导出来重用。
引用的类型对对象上的方法调用没有影响。它只掌控对象如何存储 实例数据────对象 自身的信息。哈希引用最为常见,但你可以 bless 任何类型的引用:
Moose 中创建的类由它们自己声明式地定义各自的对象属性,Perl 5 默认的 OO 则非常 宽松。一个存储球衣号和位置、代表篮球运动员的类可能会使用如下构造器:
……运动员可以这样创建:
在类的内部,方法可以直接访问哈希元素:
类之外的方法也可以这样做。这样便违反了封装────特别是,它意味着你绝不能在不 破坏外部代码的情况下改变对象的内部表示,除非投机取巧────因此,保险起见还应 提供访问器方法:
即便只有两个属性,Moose 在那些非必须代码方面显得更具吸引力。
除实例数据外,对象的另一部分就是方法分派。给定一个对象(一个 bless 后的引用), 如下形式的方法调用:
……将查找与经 bless 后的引用 $joel
相关联类的名称。在此例中,该类就是 Player
。 接下来,Perl 在 Player
包内查找一个名为 number
的函数。如果 Player
类从其 它类继承而来,Perl 也会在父类查找(如此继续)直到它找到 number
方法为止。如果存 在的话,Perl 以 $joel
作为调用物调用它。
Moose 类在元模型中存放各自的继承信息。每一个经 bless 后的引用的类将父类信息存放在 一个名为 @ISA
的包全局变量中。方法分派器会在一个类的 @ISA
中查找它的父类,以 在其中搜索合适的方法。因此,一个 InjuredPlayer
类会在其 @ISA
中包含 Player
。 你可以这样编写这重关系:
许多现存的 Perl 5 项目都这样做,但用 parent
编译命令来替代会更容易些:
可以从多个父类继承:
在解析方法分派时,Perl 5 在传统上偏向于对父类使用深度优先搜索。这就是说,如果 InjuredPlayer
从 Player
和 Hospital::Patient
两者继承,一个在 InjuredPlayer
实例上调用的方法将先分派到 InjuredPlayer
,然后是 Player
,接着经过所有 Player
的父类来到 Hospital::Patient
。
Perl 5.10 增加了一个名为 mro
的编译命令,它允许你另行使用称作 C3 的方法解析策略。 虽然特定的细节可能在处理复杂的多重继承布局时变得复杂,关键的区别是,方法解析过程将 在访问父类之前访问所有的子类。
虽然其他技巧(诸如 角色 roles 和 Moose 方法修饰符)允许你避开多重继承,但 mro
编译命令可以帮助你避免方法分派时令人惊讶的行为。可以这样在类中启用:
除非你在编写具有互操作插件的复杂框架,你几乎不会用到它。
如果在调用者及其超类的类定义内没有可用的方法,Perl 5 接下来将按照所选方法解析顺序 在每个类中查找 AUTOLOAD
函数。Perl 会调用它找到的任何 AUTOLOAD
,由此提供或 谢绝所需方法。参加 autoload 以获取更多细节。
如你所想的那样,在面对多重继承和多个候选 AUTOLOAD
目标时会变得很复杂。
与 Moose 中一样,你可以在默认的 Perl 面向对象系统中覆盖方法。不幸的是,Perl 5 核心 并没有提供指出你覆盖父类方法 意图 的机制。更糟糕的是,任何预声明、声明或导入子 类的函数都可能因重名而覆盖父类方法。你可以忘记使用 Moose 的 override
系统,但在 Perl 5 默认的面向对象系统中你根本没有这样(甚至是可选的)一重保护。
要在子类中覆盖一个方法,只需声明一个和父类方法同名的方法。在覆盖的方法内,你可以通 过 SUPER::
分派指示来调用父类方法:
方法名的 SUPER::
前缀告诉方法分派器将此方法分派到 父类 的具名实现。你可以向 其传递任何参数,但最好还是重用 @_
。
可能的话避免使用 AUTOLOAD
。如果 必须 用到,你应该用它来转发函数(functions) 定义以帮助 Perl 知道哪个 AUTOLOAD
会提供方法的实现。
使用访问器方法而非直接通过引用访问实例数据。甚至在类内部的方法体内也应该这样做。 由你自己来生成这些方法相当乏味;如果你无法使用 Moose,考虑使用诸如 Class::Accessor
这类模块来避免重复编写样板。
准备好某人某时最终将继承你的类(或委托或重新实现接口)。通过不对代码内部细节做出假定、 使用两参数形式的 bless
和把类分割为最小职责单元,可以使得他人的工作更加轻松。
不要在同一个类里混用函数和方法。
为每一个类使用单独的 .pm 文件,除非该类是一个仅用于某处的小型自包含辅助类。
考虑使用 Moose 和 Any::Moose
来替代赤裸的 Perl 5 OO;它们可以轻松和 Perl 5 对象系统 的类和对象互动,还减轻了几乎所有因类声明而带来的无趣,同时提供了更多及更好的特性。
Hey! The above document had some coding errors, which are explained below:
- Around line 5:
-
A non-empty Z<>