Spiffy - 适合您的 Spiffy Perl 界面框架
package Keen; use Spiffy -Base; field 'mirth'; const mood => ':-)'; sub happy { if ($self->mood eq ':-(') { $self->mirth(-1); print "Cheer up!"; } super; }
“Spiffy”是在 Perl 中进行面向对象 (OO) 编程的框架和方法。 Spiffy 将 Exporter.pm、base.pm、mixin.pm 和 SUPER.pm 的最佳部分结合到一个神奇的基础类中。它试图以一种干净、直接和(也许有一天)标准的方式修复传统 Perl OO 的所有缺陷。
Spiffy 借鉴了其他 OO 语言(如 Python、Ruby、Java 和 Perl 6)的思想。它还添加了一些自己的技巧。
如果你看一下 CPAN,就会发现有大量与 OO 相关的模块。当开始一个新项目时,您需要选择最有意义的模块集,然后需要在每个类中使用这些模块。另一方面,Spiffy 在一个模块中拥有您可能需要的所有内容,并且您只需在一个类中使用它一次。如果您将 Spiffy.pm 作为项目中最基础类的基类,那么 Spiffy 会自动将其所有魔力传递给您的所有子类。您最终可能会忘记您正在使用它!
Spiffy 和其他 Perl 面向对象基类之间最显着的区别是它具有导出内容的能力。如果您创建 Spiffy 的子类,则除了您想要导出的任何其他内容之外,Spiffy 导出的所有内容都将自动由您的子类导出。如果有人创建了您的子类的子类,所有这些内容都将自动导出,依此类推。将其视为“继承导出”,它使用熟悉的 Exporter.pm 规范语法。
要使用 Spiffy 或 Spiffy 的任何子类作为类的基类,请为use
命令指定-base
参数。
use MySpiffyBaseModule -base;
您还可以使用传统的use base 'MySpiffyBaseModule';
语法和一切都将完全相同。唯一需要注意的是 Spiffy.pm 必须已经加载。这是因为 Spiffy 动态地重新连接 base.pm 来完成 Spiffy 的所有魔法。
Spiffy 支持类似 Ruby 的 mixin 和类似 Perl6 的角色。就像base
一样,您可以使用以下任一调用:
use mixin 'MySpiffyBaseModule'; use MySpiffyBaseModule -mixin;
仅当混合的类是 Spiffy 的子类时,第二个版本才有效。只要 Spiffy 已经加载,第一个版本就可以在所有情况下工作。
要限制混合的方法,请使用角色。 (提示:它们的工作方式就像出口商列表):
use MySpiffyBaseModule -mixin => qw(:basics x y !foo);
在面向对象的 Perl 中,几乎每个子例程都是一个方法。每个方法都获取传递给它的对象作为其第一个参数。这意味着几乎每个子例程都以以下行开头:
my $self = shift;
Spiffy 提供了一个简单的、可选的过滤机制来为您插入该行,从而生成更清晰的代码。如果您认为平均方法有 10 行代码,那么这就是您代码的 10%!要打开此选项,只需使用- Base
选项而不是-base
选项,或添加-selfless
选项。如果源过滤让您感到不安,请不要使用该功能。我个人发现它在我追求编写干净、可维护的代码的过程中让人上瘾。
Spiffy 的一个有用功能是它导出两个函数: field
和const
,可用于声明类的属性,并自动为它们生成访问器方法。两个函数唯一的区别是const
属性不能修改;因此访问器速度要快得多。
OO 编程的一个有趣的方面是当一个方法从父类调用相同的方法时。这通常称为调用超级方法。 Perl 执行此操作的工具非常丑陋:
sub cleanup { my $self = shift; $self->scrub; $self->SUPER::cleanup(@_); }
Spiffy 使得调用超级方法变得非常容易。你只需使用super
功能即可。您不需要向它传递任何参数,因为它会自动为您传递它们。这是与 Spiffy 相同的函数:
sub cleanup { $self->scrub; super; }
Spiffy 有一个特殊的方法来解析参数,称为parse_arguments
,它也用于解析自己的参数。您可以使用两个称为boolean_arguments
和paired_arguments
的特殊方法来声明哪些参数是布尔值(单例)以及哪些参数是配对的。解析参数提取布尔值和对,并以匿名散列形式返回它们,后跟不匹配参数的列表。
最后,Spiffy 可以导出一些调试函数WWW
、 XXX
、 YYY
和ZZZ
。它们每个都会生成其参数的 YAML 转储。 WWW 警告输出,XXX 死亡并输出,YYY 打印输出,ZZZ 承认输出。如果 YAML 不适合您的需求,您可以使用-dumper
选项将所有转储切换为 Data::Dumper 格式。
那是斯皮菲!
Spiffy 在 Perl 中实现了一个全新的想法。既充当面向对象的类又导出函数的模块。但它使 Exporter.pm 的概念更进一步;它遍历类的整个@ISA
路径并遵循每个模块的导出规范。由于 Spiffy 调用 Exporter 模块来执行此操作,因此您可以使用 Exporter 拥有的所有精美界面功能,包括标签和否定。
Spiffy 考虑所有不以破折号开头的参数来构成导出规范。
package Vehicle; use Spiffy -base; our $SERIAL_NUMBER = 0; our @EXPORT = qw($SERIAL_NUMBER); our @EXPORT_BASE = qw(tire horn); package Bicycle; use Vehicle -base, '!field'; $self->inflate(tire);
在这种情况下, Bicycle->isa('Vehicle')
以及Vehicle
和Spiffy
导出的所有内容都将进入Bicycle
,除了field
。
当您设计了一个包含数百个类的系统,并且您希望它们都可以访问某些函数或常量时,导出会非常有用
or variables. Just export them in your main base class and every subclass
将获得他们需要的功能。
您几乎可以完成 Exporter 所做的所有事情,因为 Spiffy 将这项工作委托给 Exporter(在添加一些 Spiffy 魔法之后)。 Spiffy 提供了一个@EXPORT_BASE
变量,它类似于@EXPORT
,但仅适用于使用-base
的用法。
如果您在 Perl 中做过很多 OO 编程,那么您可能使用过多重继承 (MI),如果您做过很多 MI,您可能会遇到奇怪的问题和头痛。某些语言(例如 Ruby)尝试使用称为 mixins 的技术来解决 MI 问题。基本上,所有 Ruby 类仅使用单继承 (SI),然后根据需要混合其他模块的功能。
Mixin 可以简单地被认为是将另一个类的方法导入到您的子类中。但从实施的角度来看,这并不是最好的方法。 Spiffy 做 Ruby 做的事情。它创建一个空的匿名类,将所有内容导入到该类中,然后将新类链接到您的 SI ISA 路径中。换句话说,如果你说:
package AAA; use BBB -base; use CCC -mixin; use DDD -mixin;
您最终会得到一个类的单一继承链,如下所示:
AAA << AAA-DDD << AAA-CCC << BBB;
AAA-DDD
和AAA-CCC
是生成的类的实际包名称。这种风格的好处是,在 CCC 中混合不会破坏 AAA 中的任何方法,并且 DDD 也不会与 AAA 或 CCC 冲突。如果您在 CCC 中混合了 AAA 中的方法,您仍然可以使用super
来获取它。
当 Spiffy 在 CCC 中混合时,它会引入 CCC 中所有不以下划线开头的方法。事实上,事情远不止于此。如果 CCC 是子类,它将引入 CCC can
通过继承执行的所有方法。这非常强大,甚至可能太强大了。
为了限制您混合的内容,Spiffy 借用了 Perl6 中的角色概念。不过,“角色”一词在 Spiffy 中的使用更为宽松。它很像 Exporter 模块使用的导入列表,您可以使用组(标签)和否定。如果列表的第一个元素使用否定,Spiffy 将从 mixin 类可以执行的所有方法开始。
use EEE -mixin => qw(:tools walk !run !:sharp_tools);
在此示例中, walk
和run
是 EEE 可以执行的方法, tools
和sharp_tools
是 EEE 类的角色。 EEE 类如何定义这些角色?它非常简单地定义了名为_role_tools
和_role_sharp_tools
的方法,它们返回更多方法的列表。 (可能还有其他角色!)这里的巧妙之处在于,由于角色只是方法,因此它们也可以被继承。以 Perl6为例!
通过使用-Base
标志而不是-base
您永远不需要编写以下行:
my $self = shift;
使用源过滤器将此语句添加到类中的每个子例程中。其神奇之处在于简单且快速,因此创建与 Ruby 和 Python 同等的干净代码的性能损失很小。
package Example; use Spiffy -Base; sub crazy { $self->nuts; } sub wacky { } sub new() { bless [], shift; }
与以下完全相同:
package Example; use Spiffy -base; use strict;use warnings; sub crazy {my $self = shift; $self->nuts; } sub wacky {my $self = shift; } sub new { bless [], shift; } ;1;
请注意,子例程new
之后的空括号阻止其添加 $self。另请注意,额外的代码将添加到现有行中,以确保行号不被更改。
-Base
还打开严格和警告编译指示,并添加烦人的“1;”线到你的模块。
当您使用“-Base”过滤器机制时,Spiffy 现在支持私有方法。您只需使用my
关键字声明 subs,并在前面加上'$'
来调用它们。像这样:
package Keen; use SomethingSpiffy -Base; # normal public method sub swell { $self->$stinky; } # private lexical method. uncallable from outside this file. my sub stinky { ... }
XXX 函数对于调试来说非常方便,因为您几乎可以将它插入到任何地方,并且它会将您的数据转储到干净的 YAML 中。采取以下声明:
my @stuff = grep { /keen/ } $self->find($a, $b);
如果您对该语句有问题,可以通过以下任意一种方式进行调试:
XXX my @stuff = grep { /keen/ } $self->find($a, $b); my @stuff = XXX grep { /keen/ } $self->find($a, $b); my @stuff = grep { /keen/ } XXX $self->find($a, $b); my @stuff = grep { /keen/ } $self->find(XXX $a, $b);
XXX 易于插入和移除。用 XXX 标记不确定的代码区域也是一种传统。如果您忘记取出调试转储器,这将使它们很容易被发现。
WWW 和 YYY 很好,因为它们转储参数然后返回参数。通过这种方式,您可以将它们插入到许多地方,并且仍然可以像以前一样运行代码。当您需要使用 YAML 转储和完整堆栈跟踪结束时,请使用 ZZZ。
如果使用-base
选项,则默认导出调试函数,但前提是您之前使用过-XXX
选项。要导出所有 4 个函数,请使用导出标签:
use SomeSpiffyModule ':XXX';
要强制调试功能使用 Data::Dumper 而不是 YAML:
use SomeSpiffyModule -dumper;
本节介绍 Spiffy 导出的函数。仅当使用-base
或-Base
选项时,才会导出field
、 const
、 stub
和super
函数。
场地
为类的字段定义访问器方法:
package Example; use Spiffy -Base; field 'foo'; field bar => []; sub lalala { $self->foo(42); push @{$self->{bar}}, $self->foo; }
传递给field
第一个参数是正在定义的属性的名称。可以为访问器指定一个可选的默认值。如果对象中未设置该字段的值,则将返回该值。
常量
const bar => 42;
const
函数与 <field> 类似,只是它是不可变的。它也不在对象中存储数据。您可能总是想给const
一个默认值,否则生成的方法将有些无用。
存根
stub 'cigar';
stub
函数生成一个方法,该方法将带有适当的消息而结束。这个想法是子类必须实现这些方法,这样存根方法就不会被调用。
极好的
如果调用此函数时不带任何参数,它将调用 ISA 树中较高层的相同方法,并向其传递所有相同的参数。如果使用参数调用它,它将使用前面带有$self
这些参数。换句话说,它就像您期望的那样工作。
sub foo { super; # Same as $self->SUPER::foo(@_); super('hello'); # Same as $self->SUPER::foo('hello'); $self->bar(42); } sub new() { my $self = super; $self->init; return $self; }
如果没有 super 方法, super
将不执行任何操作。最后, super
在 AUTOLOAD 子例程中做了正确的事情。
本节列出了 Spiffy 的任何子类自动继承的所有方法。
混合
在运行时混合类的方法。采用与use mixin ...
相同的参数。使目标类成为调用者的混合。
$self->mixin('SomeClass'); $object->mixin('SomeOtherClass' => 'some_method');
解析参数
此方法采用参数列表并将它们分组。它允许布尔参数,该参数可能有值也可能没有值(默认为 1)。该方法返回所有对的哈希引用作为哈希中的键和值。任何无法配对的参数都会作为列表返回。这是一个例子:
sub boolean_arguments { qw(-has_spots -is_yummy) } sub paired_arguments { qw(-name -size) } my ($pairs, @others) = $self->parse_arguments( 'red', 'white', -name => 'Ingy', -has_spots => -size => 'large', 'black', -is_yummy => 0, );
在此调用之后, $pairs
将包含:
{ -name => 'Ingy', -has_spots => 1, -size => 'large', -is_yummy => 0, }
@others
将包含“红色”、“白色”和“黑色”。
布尔参数
返回被识别为布尔值的参数列表。重写此方法来定义您自己的列表。
配对参数
返回被识别为配对的参数列表。重写此方法来定义您自己的列表。
当您use
Spiffy 模块或其子类时,您可以向其传递一个参数列表。这些参数使用上述parse_arguments
方法进行解析。特殊参数-base
,用于使当前包成为正在使用的 Spiffy 模块的子类。
任何不成对的参数都像普通的导入列表一样;就像与 Exporter 模块一起使用的那些一样。
使用 Spiffy 模块作为基类的正确方法是在use
语句中使用-base
参数。这与您想要use base
典型模块不同。
package Something; use Spiffy::Module -base; use base 'NonSpiffy::Module';
现在可能很难区分什么是 Spiffy,什么不是。因此 Spiffy 实际上是为了与 base.pm 一起工作而设计的。你可以说:
package Something; use base 'Spiffy::Module'; use base 'NonSpiffy::Module';
当您的类不是实际的模块(单独的文件)而只是某个已加载的文件中的包时, use base
也非常有用。无论该类是否是模块, base
都会起作用,而-base
语法则不能那样工作,因为use
总是尝试加载模块。
为了让 Spiffy 与 base.pm 一起工作,人们玩了一个肮脏的把戏。 Spiffy 将base::import
替换为自己的版本。如果基本模块不是 Spiffy,则 Spiffy 会调用原始的 base::import。如果基本模块是 Spiffy,那么 Spiffy 会做自己的事情。
有两个注意事项。
必须先加载 Spiffy。
如果未加载 Spiffy 并且在 Spiffy 模块上调用use base
,则 Spiffy 将终止并显示一条有用的消息,告诉作者阅读此文档。这是因为 Spiffy 需要事先进行导入交换。
如果您收到此错误,只需在代码中添加这样的语句即可:
use Spiffy ();
禁止混合
base.pm
可以采用多个参数。只要所有基类都是 Spiffy,或者它们都是非 Spiffy,这都适用于 Spiffy。如果它们混合在一起,斯皮菲就会死。在这种情况下,只需使用单独的use base
语句。
Spiffy 是在 Perl 中进行 OO 编程的绝佳方法,但它仍然是一项正在进行的工作。新的东西将会被添加,而那些效果不好的东西可能会被删除。
Ingy döt Net <[email protected]>
版权所有 2004-2014。 Ingy döt 网。
该程序是免费软件;您可以按照与 Perl 本身相同的条款重新分发它和/或修改它。
请参阅http://www.perl.com/perl/misc/Artistic.html