2009/02/17

モダンPerl入門 デザインパターン実装の練習 -Iterator-

モダンPerl入門の2章では、各種デザインパターンをPerlで実装した例が出ています。
あまりデザインパターンが得意ではないので、頭の整理と練習がてら自分で書いてみたいと思います。
今日はIterator。

Iteratorは何かデータを順番に取り出していく処理を抽象化するためのパターンです。
何かのデータとは、配列であったりファイルであったり、データベースであったりという感じです。

何かの連続したデータをひとつずつ取り出すときはIteratorを使った方が良さそうということなようです。
例えば、従来は配列で持っていたデータをある日ファイルに持つようになりましたと。
そんなとき、Iteratorを使っていない場合は該当箇所をすべて直す必要があります。
しかし、Iteratorを使っていればデータを取り出すという処理が抽象化されているので、使用するオブジェクトを変更するだけで良いのです。
よかったね、よかったね、ということになると。
# あってるか不安…


書籍の例では配列とリンクリスト(Data::LinkedListという仮想のモジュールを使用)、ファイル用のIteratorを実装していました。
ひとまず、軽く実践できそうな配列とファイル(File::IOを使用)の実装をしてみました。

package MyIterator;
use Moose::Role;

requires qw(next);

no Moose::Role;

package MyIterator::Array;
use Moose;
use MooseX::AttributeHelpers;

with 'MyIterator';

has 'array' => (
metaclass => 'Collection::Array',
is => 'rw',
isa => 'ArrayRef',
required => '1',
trigger => sub {
my ($self, $list) = @_;
$self->size(scalar @$list);
},
provides => {
get => 'array_get'
},
);

has 'size' => (
is => 'rw',
isa => 'Int',
default => 0,
);

has 'current' => (
metaclass => 'Number',
is => 'rw',
isa => 'Int',
default => 0,
provides => {
add => 'current_add'
}
);

__PACKAGE__->meta->make_immutable;

no Moose;

sub next {
my $self = shift;
if ($self->current >= $self->size){
return ();
}

my $i = $self->current;
$self->current_add(1);
return $self->array_get($i);
}

1;

package MyIterator::File;
use Moose;

with 'MyIterator';

has 'filepath' => (
is => 'rw',
isa => 'Str',
required => 1,
);

has 'fh' => (
is => 'rw',
);

__PACKAGE__->meta->make_immutable;

no Moose;

use IO::File;

sub BUILD {
my $self = shift;

my $io = IO::File->new();
$io->open($self->filepath(), 'r') or die $!;
$self->fh($io);

return $self;
}

sub DESTROY{
my $self = shift;
$self->fh()->close()
}

sub next {
my $self = shift;

my $fh = $self->fh();
my $line = <$fh>;

return $line ? $line : ();
}

1;

package main;

my $iterator = MyIterator::Array->new(array => ['a', 'b', 'c']);
while (my $result = $iterator->next()) {
chomp $result;
print "$result\n";
}

$iterator = MyIterator::File->new(filepath => '/Users/hiroki/Documents/perl/iterator.pl');
while (my $result = $iterator->next()) {
chomp $result;
print "$result\n";
}


MyIteratorが今回作成するIteratorのインターフェイス。
インターフェイスはMoose::Roleを使うと簡単に実現できます。
今回はnextメソッドを必ず実装するようにしました。

MyIterator::Arrayが配列用のIterator。
データが入った配列を格納するarray、配列のサイズを格納するsize、読み込み中の現在の位置を表すcurrentのプロパティがあります。
arrayプロパティのtriggerで1回だけsizeの初期化を行うようになっています。
あとはnextメソッドが実装されています。
なるほどー、MooseとMooseX::AttributeHelpersを使うとこんなに便利なのか。

MyIterator::Fileがファイル用のIterator。
これは自分で書いた。
まず、ファイルハンドルの初期化をBUILDメソッドで行いました。
BUILDメソッドはコンストラクタで初期化された後に呼び出されるので、プロパティなどに値が入っているのは保証されています(required => 1にした場合)。
あとはnextメソッドを実装しました。
returnのところが三項演算子を使っていてちょっとトリッキーですね(これは本に書いてあった)。

で、package main以降が実際に走るコード。
最初は引数で渡した配列を順に表示してる処理です。
次が、ファイルから1行ずつ読み込んで表示する処理です。
どちらもオブジェクト作成の方法は違いますが、値を取り出す処理は一緒です。
なので、$iteratorにはMyIteratorを実装したものが入っていれば同じ処理で値が取り出せます。

次回以降はまた違うデザインパターンを実装してみます。

0 件のコメント: