跳轉到內容

使用 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 的面向物件正規化時的生產力。
華夏公益教科書