跳转到内容

使用 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.

Moose 出现之前的时代

[编辑 | 编辑源代码]
问题 在 Perl 的 Moose 时代之前,你如何解决这个问题?
答案 我们降级了,失去了Class::Accessor的实用性。

我们告诉自己,这个功能,更加高级和细化,并不需要在所有地方都使用。为了实现这个功能,你必须首先将你的模块和C::A分离。所以,这个解决方案不仅很丑陋,而且你现在又要编写访问器了。婴儿耶稣在这个时候哭了。

sub set_stupid {
	my ( $self, $uri ) = @_;
	die "bad uri" unless ref $uri eq 'URI';
	$self->{uri} = $uri;
}

调用 Moose

[编辑 | 编辑源代码]

回顾过去既枯燥又令人不安,所以现在让我们展望未来,走向光明。

一个例子

[编辑 | 编辑源代码]
package MooseObject;
use Moose;

has 'uri' => ( is => 'rw', isa => 'URI', required => 1 );

不再需要其他例子了。你现在要么向构造函数发送一个URI 对象,要么你就会死掉,并伴随着一个堆栈跟踪和 Moose 的默认消息,告诉你你需要做什么。

  1. ^ 如果你希望其他人遵循黑盒设计的原则,这同样是一个问题。黑盒设计指出你不应该修改模块的内部,即未公开、未记录的函数。使用 Moose,你可以轻松地定义你的函数的更多内容,这样如果他们决定修改它,他们的工作就会更容易。
  2. ^ @array 表示 'array' 是一个数组。然而,@$array 表示将 $array 解引用为数组。因此,'@' 将意味着数据类型是一个数组,或者它指向一个数组,并且应该通过解引用来强制转换。
  3. ^ 不要像个讨厌的孩子一样指出类型约束与访问器无关,如果你称之为膨胀,那么我的愚蠢导弹就会杀了你。
  4. ^ 我们当然是指使用 Perl 的面向对象范式时的生产力。
华夏公益教科书