这是 perlootut 命令,可以使用我们的多个免费在线工作站之一在 OnWorks 免费托管服务提供商中运行,例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器
程序:
您的姓名
perlootut - Perl 中的面向对象编程教程
日期
本文档创建于 2011 年 XNUMX 月,最后一次重大修订是在 XNUMX 月,
2013.
如果您将来正在阅读本文,那么最先进的技术可能已经
改变了。 我们建议您首先阅读最新稳定版中的 perlootut 文档
Perl 的发行版,而不是这个版本。
商品描述
本文档介绍了 Perl 中的面向对象编程。 它始于
简要概述面向对象设计背后的概念。 然后介绍了
来自 CPAN 的几个不同的面向对象系统http://search.cpan.org> 建立在什么之上
Perl 提供。
默认情况下,Perl 的内置 OO 系统非常小,让您可以完成大部分工作。
这种极简主义在 1994 年很有意义,但在 Perl 5.0 之后的几年里,我们已经看到了
Perl OO 中出现了许多常见模式。 幸运的是,Perl 的灵活性允许
Perl OO 系统的丰富生态系统蓬勃发展。
如果你想知道 Perl OO 在底层是如何工作的,perlobj 文档解释了
细枝末节的细节。
本文档假设您已经了解 Perl 语法、变量
类型、运算符和子程序调用。 如果你还不明白这些概念,请
首先阅读 perlintro。 您还应该阅读 perlsyn、perlop 和 perlsub 文档。
面向对象 基本面
大多数对象系统共享许多共同的概念。 你可能听说过这样的术语
之前的“类”、“对象”、“方法”和“属性”。理解这些概念将使
它更容易阅读和编写面向对象的代码。 如果你已经熟悉
这些术语,您仍然应该略读本节,因为它用术语解释了每个概念
Perl 的 OO 实现。
Perl 的面向对象系统是基于类的。 基于类的面向对象是相当普遍的。 它被 Java、C++、
C#、Python、Ruby 和许多其他语言。 还有其他面向对象范式
以及。 JavaScript 是最流行的使用另一种范式的语言。 JavaScript 的面向对象
系统是基于原型的。
摆件
An 对象 是一种数据结构,将数据和操作的子程序捆绑在一起
那个数据。 一个对象的数据被称为 属性,其子程序被称为 方法.
一个对象可以被认为是一个名词(一个人、一个网络服务、一台计算机)。
一个对象代表一个单独的离散事物。 例如,一个对象可能代表一个
文件。 文件对象的属性可能包括其路径、内容和最后
修改时间。 如果我们创建了一个对象来表示 在/ etc /主机名 在名为的机器上
“foo.example.com”,该对象的路径将是“在/ etc /主机名",它的内容是
"foo\n",它的最后一次修改时间是从开始算起的 1304974868 秒
时代的。
与文件关联的方法可能包括“rename()”和“write()”。
在 Perl 中,大多数对象都是散列,但我们推荐的 OO 系统使您不必
担心这个。 在实践中,最好考虑一个对象的内部数据结构
不透明。
增益级
A 程 定义一类对象的行为。 类是类别的名称
(如“文件”),并且一个类还定义了该类别中对象的行为。
所有对象都属于一个特定的类。 例如,我们的 在/ etc /主机名 对象属于
“文件”类。 当我们想要创建一个特定的对象时,我们从它的类开始,然后
建设 or 实例化 一个东西。 一个特定的对象通常被称为 例
一个班级。
在 Perl 中,任何包都可以是一个类。 作为类的包和
一个不是基于包的使用方式。 这是我们的“类声明”
“文件”类:
包文件;
在 Perl 中,没有用于构造对象的特殊关键字。 但是,大多数 OO 模块
在 CPAN 上使用名为“new()”的方法来构造一个新对象:
我的 $hostname = File->new(
路径 => '在/ etc /主机名',
内容 => "foo\n",
last_mod_time => 1304974868,
);
(不用担心那个“->”操作符,后面会解释。)
祝福
正如我们之前所说,大多数 Perl 对象都是散列,但一个对象可以是任何
Perl 数据类型(标量、数组等)。 把一个普通的数据结构变成一个对象是
做完了 祝福 该数据结构使用 Perl 的“bless”函数。
虽然我们强烈建议您不要从头开始构建对象,但您应该了解
术语 保佑。 一个 幸福 数据结构(又名“参照物”)是一个对象。 我们有时会说
一个对象已经被“祝福成一个类”。
一旦所指对象被祝福,Scalar::Util 核心模块中的“blessed”函数
可以告诉我们它的类名。 这个子程序在传递一个对象时返回一个对象的类
对象,否则为假。
使用 Scalar::Util 'blessed';
打印有福的($hash); # 未定义
打印祝福($主机名); # 文件
构造函数
A 构造函数 创建一个新对象。 在 Perl 中,类的构造函数只是另一种方法,
与其他一些为构造函数提供语法的语言不同。 大多数 Perl 类使用
"new" 作为其构造函数的名称:
我的 $file = File->new(...);
方法
你已经知道一个 方法 是对对象进行操作的子程序。 你可以
将方法视为对象可以做的事情 do. 如果一个对象是一个名词,那么
方法是它的动词(保存、打印、打开)。
在 Perl 中,方法只是存在于类包中的子例程。 方法是
总是写入接收对象作为他们的第一个参数:
子打印信息{
我的 $self = shift;
print "此文件位于 ", $self->path, "\n";
}
$file->print_info;
# 文件在 在/ etc /主机名
使方法特别的是 形成一种 它的 被称为. 箭头操作符(“->”)告诉 Perl
我们正在调用一个方法。
当我们进行方法调用时,Perl 会安排该方法的 祈求者 作为通过
第一个论点。 祈求者 是箭头左侧事物的奇特名称。 这
invocant 可以是类名或对象。 我们还可以将额外的参数传递给
方法:
子打印信息{
我的 $self = shift;
my $prefix = shift // "这个文件在 ";
打印 $prefix, ", ", $self->path, "\n";
}
$file->print_info("文件位于");
# 文件位于 在/ etc /主机名
Attributes
每个类都可以定义它的 属性. 当我们实例化一个对象时,我们将值分配给
那些属性。 例如,每个“文件”对象都有一个路径。 属性有时
被称为 .
Perl 没有特殊的属性语法。 在幕后,属性通常存储为
对象的底层散列中的键,但不要担心这一点。
我们建议您仅通过以下方式访问属性 存取器 方法。 这些方法是
可以获取或设置每个属性的值。 我们早些时候在“print_info()”中看到了这一点
例如,它调用“$self->path”。
您可能还会看到条款 吸气剂 和 二传手. 这是两种类型的访问器。 吸气剂
获取属性的值,而 setter 设置它。 setter 的另一个术语是 突变体
属性通常定义为只读或读写。 只读属性只能
在第一次创建对象时设置,而读写属性可以随时更改
时间。
属性的值本身可能是另一个对象。 例如,而不是返回
它的最后一个修改时间作为一个数字,“文件”类可以返回一个 DateTime 对象
代表那个值。
可能有一个不公开任何可公开设置的属性的类。 不是
每个类都有属性和方法。
多态性
多态性 是一种奇特的说法,即来自两个不同类的对象共享一个
应用程序接口。 例如,我们可以有“File”和“WebPage”类,它们都有一个
“print_content()”方法。 这种方法可能会为每个类产生不同的输出,但是
它们共享一个通用接口。
虽然这两个类可能在很多方面有所不同,但当涉及到“print_content()”
方法,都是一样的。 这意味着我们可以尝试调用“print_content()”方法
在任一类的对象上,以及 we 别 它们在许多情况下都能提供类似的结果。 至 知道 什么 程 此 对象 属于 至!
多态性是面向对象设计的关键概念之一。
遗产
遗产 允许您创建现有类的专用版本。 继承让
新类重用了另一个类的方法和属性。
例如,我们可以创建一个“File::MP3”类 继承 从文件”。 一个
“文件::MP3” 是 更多 具体的 “文件”类型。 所有的mp3文件都是文件,但不是所有的文件
是mp3文件。
我们经常将继承关系称为 父母与子女 或“超类/子类”
关系。 有时我们说孩子有一个 是 与其父母的关系
类。
“文件”是一个 超类 "File::MP3" 和 "File::MP3" 是一个 子类 的“文件”。
包文件::MP3;
使用父“文件”;
父模块是 Perl 允许您定义继承的几种方式之一
关系。
Perl 允许多重继承,这意味着一个类可以从多个继承
父母。 虽然这是可能的,但我们强烈建议不要这样做。 一般来说,你可以使用
角色 用多重继承做你能做的一切,但要以更简洁的方式。
请注意,定义给定类的多个子类并没有错。 这
既常见又安全。 例如,我们可以定义“File::MP3::FixedBitrate”和
“File::MP3::VariableBitrate”类来区分不同类型的mp3文件。
覆写 方法 和 方法 分辨率
继承允许两个类共享代码。 默认情况下,父类中的每个方法
也可用于儿童。 孩子可以明确 覆盖 父母的方法
提供自己的实现。 例如,如果我们有一个“File::MP3”对象,它有
“文件”中的“print_info()”方法:
我的 $cage = File::MP3->new(
路径 => 'mp3s/My-Body-Is-a-Cage.mp3',
内容 => $mp3_data,
last_mod_time => 1304974868,
title => '我的身体是笼子',
);
$cage->print_info;
# 该文件位于 mp3s/My-Body-Is-a-Cage.mp3
如果我们想在问候语中包含 mp3 的标题,我们可以覆盖该方法:
包文件::MP3;
使用父“文件”;
子打印信息{
我的 $self = shift;
print "此文件位于 ", $self->path, "\n";
print "它的标题是", $self->title, "\n";
}
$cage->print_info;
# 该文件位于 mp3s/My-Body-Is-a-Cage.mp3
# 它的标题是我的身体是笼子
确定应该使用什么方法的过程称为 方法 分辨率。 什么
Perl 首先查看对象的类(在本例中为“File::MP3”)。 如果那个班
定义方法,然后调用该类的方法版本。 如果没有,Perl 看起来
依次在每个家长班上。 对于“File::MP3”,它唯一的父级是“File”。 如果“文件:: MP3”
没有定义方法,但“文件”定义了,然后 Perl 调用“文件”中的方法。
如果“File”继承自“DataSource”,而“DataSource”又继承自“Thing”,那么 Perl 将保留
如有必要,请查看“链条”。
可以从子级显式调用父级方法:
包文件::MP3;
使用父“文件”;
子打印信息{
我的 $self = shift;
$self->SUPER::print_info();
print "它的标题是", $self->title, "\n";
}
"SUPER::" 位告诉 Perl 在 "File::MP3" 类中查找 "print_info()"
继承链。 当它找到实现该方法的父类时,该方法
叫做。
我们之前提到了多重继承。 多重继承的主要问题是
它使方法解析大大复杂化。 有关更多详细信息,请参阅 perlobj。
封装
封装 是物体不透明的想法。 当其他开发人员使用您的
类,他们不需要知道 形成一种 它已实施,他们只需要知道 什么 是的。
出于多种原因,封装很重要。 首先,它允许您将
来自私有实现的公共 API。 这意味着您可以更改该实现
在不破坏 API 的情况下。
其次,当类被很好地封装时,它们变得更容易子类化。 理想情况下,一个
子类使用相同的 API 来访问其父类使用的对象数据。 事实上,
子类化有时会违反封装,但一个好的 API 可以最大限度地减少
需要这样做。
我们之前提到过,大多数 Perl 对象都是在底层实现为散列的。 这
封装原则告诉我们,我们不应该依赖于此。 相反,我们应该
使用访问器方法访问该哈希中的数据。 我们推荐的对象系统
下面所有自动生成访问器方法。 如果你使用其中之一,你应该
永远不必直接以散列形式访问对象。
组成成分
在面向对象的代码中,我们经常发现一个对象引用了另一个对象。 这是
被称为 写作,或 有一个 关系。
早些时候,我们提到“文件”类的“last_mod_time”访问器可以返回一个
日期时间对象。 这是一个完美的组合示例。 我们可以走得更远,而且
使“路径”和“内容”访问器也返回对象。 “文件”类将
然后是 由 其他几个对象。
角色
角色 是一类 不, 而不是它 is. 角色是
Perl 相对较新,但已变得相当流行。 角色是 应用的 到班级。
有时我们说类 消耗 的角色。
角色是提供多态性的继承的替代方案。 假设我们有
两个类,“无线电”和“计算机”。 这两个东西都有开/关开关。 我们想
在我们的类定义中建模。
我们可以让两个类都从一个共同的父类继承,比如“机器”,但不是全部
机器有开/关开关。 我们可以创建一个名为“HasOnOffSwitch”的父类,但是
这是非常人为的。 收音机和计算机不是这个父母的专业。
这个父母真是一个相当可笑的创造。
这就是角色的用武之地。创建“HasOnOffSwitch”角色和
将其应用于两个类。 此角色将定义一个已知的 API,例如提供“turn_on()”
和“turn_off()”方法。
Perl 没有任何内置的方式来表达角色。 过去,人们只是咬
子弹并使用多重继承。 现在,CPAN 上有几个不错的选择,用于
使用角色。
什么时候 至 使用 OO
面向对象并不是所有问题的最佳解决方案。 在 Perl的 最棒的 行为准则
(版权所有 2004,由 O'Reilly Media, Inc. 出版),Damian Conway 提供了一份清单
在决定 OO 是否适合您的问题时使用的标准:
· 正在设计的系统很大,或者可能会变大。
· 数据可以聚合成明显的结构,特别是如果有一个大的
每个聚合中的数据量。
· 各类数据聚合形成自然层次,方便使用
继承和多态。
· 您有一段数据,对其应用了许多不同的操作。
· 您需要对相关类型的数据执行相同的一般操作,但使用
根据应用操作的特定数据类型,略有不同
至。
· 很可能您以后必须添加新的数据类型。
· 数据片段之间的典型交互最好由运算符来表示。
· 系统各个组件的实现可能会发生变化
时间。
· 系统设计已经是面向对象的。
· 大量其他程序员将使用您的代码模块。
PERL OO 系统
正如我们之前提到的,Perl 的内置 OO 系统非常小,但也相当
灵活的。 多年来,许多人开发了建立在 Perl 之上的系统
内置系统提供更多功能和便利。
我们强烈建议您使用这些系统之一。 即使是最小的
消除了大量重复的样板文件。 真的没有充分的理由写你的
在 Perl 中从头开始类。
如果您对这些系统的基础感兴趣,请查看 perlobj。
驼鹿
Moose 将自己标榜为“Perl 5 的后现代对象系统”。 不要害怕,该
“后现代”标签是对拉里将 Perl 描述为“第一个后现代
计算机语言”。
“Moose”提供了一个完整的、现代的面向对象系统。 它最大的影响是 Common Lisp
对象系统,但它也借鉴了 Smalltalk 和其他几种语言的思想。
“Moose”由 Stevan Little 创建,大量借鉴了他在 Perl 6 OO 上的工作
设计。
这是我们使用“Moose”的“File”类:
包文件;
使用驼鹿;
有路径 => ( is => 'ro' );
有内容 => ( is => 'ro' );
有 last_mod_time => ( is => 'ro' );
子打印信息{
我的 $self = shift;
print "此文件位于 ", $self->path, "\n";
}
“Moose”提供了许多功能:
· 声明糖
“Moose”为定义类提供了一层声明性的“糖”。 那个糖是
只是一组导出的函数,使声明您的类的工作方式更简单和
更可口。 这让你描述 什么 你的课程是,而不必告诉
Perl的 形成一种 实现你的类。
“has()”子程序声明一个属性,“Moose”自动创建
这些属性的访问器。 它还负责为
你。 这个构造函数知道你声明的属性,所以你可以设置它们
创建新的“文件”时。
· 内置角色
“Moose”让你可以像定义类一样定义角色:
包 HasOnOfSwitch;
使用 Moose::Role;
有 is_on => (
是 => 'rw',
isa => '布尔',
);
子开启{
我的 $self = shift;
$self->开启(1);
}
子关闭{
我的 $self = shift;
$self->开启(0);
}
· 一个微型的类型系统
在上面的例子中,你可以看到我们将 "isa => 'Bool'" 传递给 "has()" 当
创建我们的“is_on”属性。 这告诉“Moose”这个属性必须是一个
布尔值。 如果我们尝试将其设置为无效值,我们的代码将抛出错误。
·充分的反省和操纵
Perl 的内置自省功能相当少。 “驼鹿”建立在
它们并为您的类创建一个完整的内省层。 这让你问
诸如“File 类实现了哪些方法?”之类的问题。 它还可以让你修改
您的课程以编程方式。
· 自托管和可扩展
“Moose”使用自己的自省 API 来描述自己。 除了是一个很酷的技巧,
这意味着您可以使用“Moose”本身来扩展“Moose”。
· 丰富的生态系统
在 MooseX 下的 CPAN 上有丰富的“Moose”扩展生态系统
<http://search.cpan.org/search?query=MooseX&mode=dist> 命名空间。 此外,许多
CPAN上的模块已经使用了“Moose”,为你提供了大量的例子来学习
从。
· 更多功能
“Moose”是一个非常强大的工具,我们不能在这里涵盖它的所有功能。 我们
鼓励您通过阅读“Moose”文档来了解更多信息,从
驼鹿::手册http://search.cpan.org/perldoc?Moose::手册>。
当然,“Moose”并不完美。
“Moose”会使您的代码加载速度变慢。 “驼鹿”本身不小,它的作用是 很多
定义类时生成代码。 此代码生成意味着您的
运行时代码尽可能快,但是当你的模块第一次出现时你会为此付出代价
已加载。
当启动速度很重要时,这种加载时间可能会成为问题,例如
命令行脚本或每次加载时都必须加载的“普通”CGI 脚本
被执行。
在您惊慌之前,要知道很多人确实使用“Moose”作为命令行工具和其他工具
启动敏感代码。 我们鼓励您在担心之前先尝试“Moose”
启动速度。
“Moose”还依赖于其他模块。 其中大部分是小摊
单独的模块,其中一些模块是从“Moose”中分离出来的。 “驼鹿”本身,还有一些
它的依赖项,需要一个编译器。 如果您需要在系统上安装软件
没有编译器,或者如果有 任何 依赖是一个问题,那么“Moose”可能不是
适合你
哞哞
如果您尝试“Moose”并发现其中一个问题阻止您使用“Moose”,
我们鼓励您接下来考虑 Moo。 “Moo”实现了“Moose”的一个子集
更简单的包中的功能。 对于它确实实现的大多数功能,最终
用户 API 是 相同 到“Moose”,这意味着您可以完全从“Moo”切换到“Moose”
容易。
"Moo" 没有实现 "Moose" 的大部分自省 API,所以它通常更快
加载你的模块。 此外,它的任何依赖项都不需要 XS,因此可以
安装在没有编译器的机器上。
“Moo”最引人注目的功能之一是它与“Moose”的互操作性。 当某人
尝试在“Moo”类或角色上使用“Moose”的自省 API,它是透明的
膨胀为“驼鹿”类或角色。 这使得合并“Moo”更容易
代码转换为“Moose”代码库,反之亦然。
例如,“Moose”类可以使用“extends”子类化“Moo”类或使用“Moo”
角色使用“with”。
“Moose”作者希望有一天通过改进“Moose”可以让“Moo”过时
足够了,但就目前而言,它为“Moose”提供了一个有价值的替代方案。
类::访问器
Class::Accessor 是“Moose”的对立面。 它提供的功能很少,也不是
自托管。
但是,它非常简单,纯 Perl,并且没有非核心依赖项。 它也是
为其支持的功能按需提供“类似Moose”的API。
即使它没有做太多事情,仍然最好从编写自己的类
刮。
这是我们带有“Class::Accessor”的“File”类:
包文件;
使用 Class::Accessor '鹿角';
有路径 => ( is => 'ro' );
有内容 => ( is => 'ro' );
有 last_mod_time => ( is => 'ro' );
子打印信息{
我的 $self = shift;
print "此文件位于 ", $self->path, "\n";
}
“鹿角”导入标志告诉“Class::Accessor”你想定义你的属性
使用类似“Moose”的语法。 您可以传递给“has”的唯一参数是“is”。 我们
如果您选择“Class::Accessor”,建议您使用这种类似 Moose 的语法,因为它
意味着如果您以后决定转向“Moose”,您将拥有更顺畅的升级路径。
像“Moose”一样,“Class::Accessor”为你生成访问器方法和构造函数
类。
类::小
最后,我们有 Class::Tiny。 这个模块名副其实。 它有一个令人难以置信的
最少的 API 并且绝对不依赖任何最新的 Perl。 尽管如此,我们认为这是很多
比从头编写自己的 OO 代码更容易使用。
这是我们的“文件”类:
包文件;
使用 Class::Tiny qw( 路径内容 last_mod_time );
子打印信息{
我的 $self = shift;
print "此文件位于 ", $self->path, "\n";
}
这就是它!
使用“Class::Tiny”,所有访问器都是读写的。 它为您生成一个构造函数,如
以及您定义的访问器。
您还可以将 Class::Tiny::Antlers 用于类似“Moose”的语法。
角色::小
正如我们之前提到的,角色提供了继承的替代方案,但 Perl 没有
有任何内置的角色支持。 如果您选择使用 Moose,它会配备成熟的
角色实现。 但是,如果您使用我们推荐的其他 OO 模块之一,您可以
仍然使用 Role::Tiny 的角色
"Role::Tiny" 提供了一些与 Moose 的角色系统相同的功能,但在很多方面
较小的包。 最值得注意的是,它不支持任何类型的属性声明,所以
你必须手工完成。 尽管如此,它还是很有用的,并且与“Class::Accessor”配合得很好
和“类::小”
OO 系统 总结
以下是我们涵盖的选项的简要回顾:
·驼鹿
“驼鹿”是最大的选择。 它有很多功能,一个庞大的生态系统,以及
蓬勃发展的用户群。 我们还简要介绍了 Moo。 “Moo”是“Moose”精简版,一个
当 Moose 不适用于您的应用程序时的合理选择。
· 类::访问器
“Class::Accessor”比“Moose”做的少很多,如果你发现它是一个不错的选择
“驼鹿”势不可挡。 它已经存在了很长时间,并且经过了充分的战斗测试。 它也是
具有最小的“Moose”兼容模式,可以从“Class::Accessor”移动到
“驼鹿”容易。
· 类::小
"Class::Tiny" 是绝对最小的选项。 它没有依赖关系,几乎没有
语法学习。 对于超小型环境和投掷来说,这是一个不错的选择
无需担心细节,可以快速组合在一起。
· 角色::小
如果您发现自己,请使用“Role::Tiny”和“Class::Accessor”或“Class::Tiny”
考虑多重继承。 如果你和“驼鹿”一起去,它就有了自己的作用
实施。
其他 OO 产品
除了这里介绍的那些之外,CPAN 上还有许多其他与 OO 相关的模块,
如果您使用其他人的代码,您很可能会遇到其中的一个或多个。
此外,大量代码“手工”完成所有面向对象的操作,仅使用 Perl
内置 OO 功能。 如果你需要维护这样的代码,你应该阅读 perlobj
准确理解 Perl 的内置 OO 是如何工作的。
结论
正如我们之前所说,Perl 的最小 OO 系统导致了 CPAN 上大量的 OO 系统。
虽然您仍然可以直接使用裸机并手动编写类,但
真的没有理由用现代 Perl 来做到这一点。
对于小型系统,Class::Tiny 和 Class::Accessor 都提供最小的对象系统
为您处理基本样板。
对于更大的项目,Moose 提供了一组丰富的功能,让您专注于
实现您的业务逻辑。
我们鼓励您使用并评估 Moose、Class::Accessor 和 Class::Tiny 以了解
哪种 OO 系统适合您。
使用 onworks.net 服务在线使用 perlootut