[僕] perl で Observer パターン

僕ト云フ事

たろマークはてなブックマーク

2009年10月12日

[designpattern][moose][perl] perl で Observer パターン

Ruby で Observer パターンをやってみたところで、Perl でもやってみたくなったので書いてみました。お題はコードの世界に載っているテキスト表示の時計です。

ruby にある observer モジュールは include で mixin して使うので、Perl では Moose の Role を使って実装してみます。(モダン Perl 入門にもデザパタの章で Observer パターンがありますが、あえて ruby の observer モジュールを模してみます。)

で、これが Observable な Role です。これは時計の心臓部 Tick の Role なので Tick::Role::Observable としました。

package Tick::Role::Observable;
 
use Moose::Role;
 
has observer => (
    is => 'rw',
    default => sub { {} },
);
 
has state => (
    is => 'rw',
    default => 0,
);
 
sub is_changed {
    my ($self) = @_;
    return $self->state();
}
 
sub changed {
    my ($self) = @_;
    $self->state(1);
}
 
sub notify_observers {
    my ($self, @args) = @_;
 
    return unless $self->is_changed;
    $self->state(0);
 
    foreach my $package ( keys %{$self->observer} ) {
        eval {
            $self->observer->{$package}->update(@args);
        };
        if ( $@ ) {
            confess $@;
        }
    }
 
    return 1;
}
 
sub add_observer {
    my ($self, $obj) = @_;
 
    my $package = ref $obj;
    confess $obj . 'is not object'
        unless $package;
 
    confess $package . 'can not update'
        unless $obj->can('update');
 
    $self->observer->{$package} = $obj;
}
 
1;

このモジュールを with 'Tick::Role::Observable'; するとそのモジュールに Observer な振る舞いが付与されます。

元のお題の時計モジュールはこんな感じ。

package Tick;
 
use Moose;
 
with 'Tick::Role::Observable';
 
__PACKAGE__->meta->make_immutable();
 
no Moose;
 
use DateTime;
use DateTime::TimeZone;
use Time::HiRes qw(sleep gettimeofday);
 
sub start {
    my ($self, @args) = @_;
 
    my $tz = DateTime::TimeZone->new( name => 'Asia/Tokyo' );
    while (1) {
        my $now = DateTime->now( time_zone => $tz );
        $self->changed();
        $self->notify_observers($now->hour, $now->minute, $now->second);
 
        # micro second 単位のズレを調整
        sleep 1.0 - (gettimeofday)[1] / 1000000.0;
    }
}
 
1;

Tick->new->start すると1秒ごと sleep するループに陥ります。
1秒ごとに changed メソッドで更新フラグを立てて、notify_observers メソッドで監視者のオブジェクトに更新を通知します。

では、監視者として時刻をテキスト表示するオブジェクトを用意します。

package TextClock;
 
use Moose;
 
__PACKAGE__->meta->make_immutable();
 
no Moose;
 
sub update {
    my ($self, @args) = @_;
 
    local $| = 1;
    printf "\e[8D%02d:%02d:%02d", @args;
}
 
1;

Observable なモジュールの監視者になるには update メソッドが必要です。
notify_observer メソッドで更新通知される際には update メソッドが呼ばれるからです。
なので、 TextClock モジュールにも update メソッドを用意します。
引数には notify_observer メソッドに渡された引数が全部渡ってきます。

さて、これで時計の心臓部である Tick モジュールと TextClock モジュールができました。この二つを監視者と被監視者としてスクリプトにまとめてみます。

use Tick;
use TextClock;
 
my $tick = Tick->new();
$tick->add_observer( TextClock->new() );
$tick->start();

add_observer に update メソッドを持ったオブジェクトを渡してあげると、監視者と被監視者の関係が簡単にできます。
このスクリプトを実行すると現在時刻がテキストで表示され、1秒ごとに更新されます。

肝は接点が add_observer メソッドだけなところですね。
時計の表示を変えたくなったら update メソッドを持ったオブジェクトを作って add_observer してあげるだけで良くなります。(とコードの世界に書いてあった)

自分が普段使ってる言語で書き直せたらそれなりに理解できてるかしら?

まつもとゆきひろ コードの世界‾スーパー・プログラマになる14の思考法
まつもとゆきひろ
日経BP出版センター
売り上げランキング: 44091
おすすめ度の平均: 4.0
4 Rubyに導入された思考法
モダンPerl入門 (CodeZine BOOKS)
牧 大輔
翔泳社
売り上げランキング: 18356
おすすめ度の平均: 5.0
5 perl経験者は読んで損はしない
5 Perl中級者におすすめしたい

トラックバック

このエントリーのトラックバックURL:
http://vkgtaro.jp/cgi-bin/mt/mt-tb.cgi/668

コメントを投稿