使用 Moose 编程/解决的问题/类型系统
Perl 缺少,而 Moose 拥有。 至少,不是那么多。
回到 Perl 的问题,更具体地说,是对象系统没有涵盖的另一个特性,而是作为 Moose 的恩赐提供的。所以在这个例子中,你正在编写一个程序,就像一个优秀的程序员一样,你所有的 URL 都由URI
对象处理,你所有的方法都对URI
对象进行操作 - 或者至少它们认为它们是在对URI
对象进行操作。让方法依赖于URI
对象是一件好事。相信我,它很好,另一种选择就是天堂-1。不幸的是,当你将模块发布到一个充满智力低下的人的世界时,你没有简便的方法来确保他们只发送 URI。[1]
这又是 Perl 缺少的地方。所有 URL 都存储为URI
对象,但你无法轻松地确保它们仅为URI
对象。
思考一下符号的作用。符号的副作用之一是,函数在编译时就知道发送给它的输入是否在正确的范围内。Perl 的符号有点更像巫术,因为有时它们可以作为运算符来改变值。[2] 让我们看看 Perl 如何处理输入验证。
my %foo = ( foo => bar );
# This will not compile because the CORE::values function
# only operates on a hash, and not an array as indicated
# by the sigil. See perl's prototypes for more info.
print for values @foo;
在下面的例子中,类 HashOperations 期望一个 HashObject
my $foo = HashObject->new;
$foo = 'stupid_mistake';
## Stupid non-intelligent error will happen here
## because the user made a stupid mistake.
HashOperations->load( $foo )->values;
所以这里的问题是,过程式的非引用形式将在编译时被捕获,而所有其他对象变体都不会。
解决这个问题的旧的 Perl 方法实际上是在嘲笑用户。核心团队宁愿让程序员使用模块化的语言插件,而不是向核心添加特定功能,或者对其进行彻底修改,这样生产力就完全依赖于插件。[3] 我们可以推测一下缺乏类型约束功能的原因。
- Perl 没有其他解释型语言可以作为模型,更不用说具有基本对象类型的解释型语言了。
- Perl 的引用早于其对象。
- 一个哈希是一个哈希(标量的哈希),一个数组是一个数组(标量的数组)。这其中很大一部分可以通过使用 Perl 的符号在编译时确定。而对象完全是在运行时发生的,并且没有被赋予唯一的符号。
在这组例子中,我们将展示为什么 Class::Accessor 在创建访问器方面自动地比 Moose 逊色,因为 Moose 能够指定有效的类型。[4]
这里我们创建一个自定义数据类型,即 NonMooseObject
package NonMooseObject;
use strict;
use warnings;
use base 'Class::Accessor';
BEGIN { __PACKAGE__->mk_accessors( 'uri' ) };
sub new {
my ( $class, $hash ) = @_;
my $self = bless $hash || {}, $class;
$self;
}
这是你希望用户做的事情
package main;
my $uri = URI->new( 'http://moose.com' );
my $obj = NonMooseObject->new({ uri => $uri });
$obj->uri; ## prints php.
$obj->uri->path; ## joy
这是你不想让用户做的事情。在这个例子中,我们展示了 NonMooseObject 如何容易被滥用。有一件很重要的事情很难在例子中展示:死亡可能会也可能不会在调用->new
时发生。希望,对于用户和程序员来说,它是在调用 new 时死亡的;但是,假设你运行了 42 天,然后一些内部的东西在$obj->uri
上调用->path
,而->uri
不包含 URI 对象。结果就是可能会更难调试的错误。
package main;
my $obj = NonMooseObject->new({ uri => 'http://perl.com' });
$obj->uri; ## prints php.
$obj->uri->path; ## dies a horrid runtime death.
问题 | 在 Perl 的 Moose 时代之前,你如何解决这个问题? |
---|---|
答案 | 我们降级了,失去了Class::Accessor 的实用性。 |
我们告诉自己,这个功能,更加高级和细化,并不需要在所有地方都使用。为了实现这个功能,你必须首先将你的模块和C::A
分离。所以,这个解决方案不仅很丑陋,而且你现在又要编写访问器了。婴儿耶稣在这个时候哭了。
sub set_stupid {
my ( $self, $uri ) = @_;
die "bad uri" unless ref $uri eq 'URI';
$self->{uri} = $uri;
}
如果你发现自己在读完这本书后使用 ref ,你需要立即停止编程,找到一个新的职业。 |
回顾过去既枯燥又令人不安,所以现在让我们展望未来,走向光明。
package MooseObject;
use Moose;
has 'uri' => ( is => 'rw', isa => 'URI', required => 1 );
不再需要其他例子了。你现在要么向构造函数发送一个URI
对象,要么你就会死掉,并伴随着一个堆栈跟踪和 Moose 的默认消息,告诉你你需要做什么。