しかし、オブジェクトはまったく予想できない動作をするわけではない。この 場合でも、この一連の動作は一種のループになっていて、ループの各点では 同じ動作をすると期待される。このようなループがオブジェクトライフサイクル を構成する。
「扉を閉める」「スィッチ」などがオブジェクトの状態を変えるきっかけ である。これらを「イベント」と呼ぶのが普通である。オブジェクトの状態は イベントによって変わる、あるいは、オブジェクトの状態を変えるものが イベントである。
この(個々の)オブジェクトの振る舞いを表すのには、状態遷移図を使うのが 普通である。例えば、一番簡単な例として電灯の状態遷移図を書いてみよう。
package Signal; sub new { my $type = shift; my %params = @_; my $self = {}; $self->{'State'} = $params{'State'}; $self->{'State'} = defined($params{'State'})? $params{'State'}:'red'; $self->{'States'} = ['green','yellow','red']; bless $self; } sub next { my ($self) = shift; my $state = $self->{'State'}; if($state eq 'red') { $self->{'State'} = 'green'; } elsif($state eq 'green') { $self->{'State'} = 'yellow'; } else { $self->{'State'} = 'red'; # Red for all other cases } # return $self->{'State'}; } sub print { my ($self) = shift; my $state = $self->{'State'}; print $state; }これは以下のように実行する。
DB<3> $a = Signal->new(State=>Red); DB<4> $a->print DB<5> $a->next DB<6> $a->print red DB<7> $a->next DB<8> $a->print green通常は、このように明示的に状態が表されることは少ない。
しかし、それは人間の手に負えないほど複雑なものであるのが普通である。
たとえば、2状態のオブジェクトが10個あったとすると、その積は2^10=1024
の状態を持つ。
したがって、個々のオブジェクトの状態だけでなく、システム全体の振る舞いを
見通し良くするための表現が必要だと考えられる。それには協調図などを
用いることになる。
my $main = MainWindow->new();とすれば、MainWindowができる。MainWindowのインスタンスとして、いろいろな ものを使うことができる。
Label | 文字を印として置く |
Text | 編集用のテキスト |
Entry | 入力カラム |
Canvas | 図形表示 |
Frame | いくつかのwidgetをまとめる |
oval | 楕円 |
rectangle | 箱 |
line | 線 |
polygon | 多角形 |
bitmap | イメージ |
text | テキスト |
$canv->move($ball, $deltax, $deltay); | 移動 |
$canvas->itemconfigure($view,-fill=>'black'); | 色などの変更 |
$canvas->delete($view); | 削除 |
信号機を表示する Perl/Tkのプログラムは以下のようになる。
package SignalView; use Tk; sub new { my $type = shift; my %params = @_; my $self = {}; my $signal = Signal->new(%params); my $main = $params{-main}; my $canvas; my $states = $signal->{'States'}; my %views = (); if(! defined( $main)) { $main = MainWindow->new(%params); } $canvas = $main->Canvas(-width=>50,-height=>150); $x = 5; $y = 5; $r = 40; for $state ( @$states ) { $views{$state} = $canvas->create('oval', $x, $y, $x+$r, $y+$r, -width => 1, -outline => 'black', -fill => 'black'); $y += 50; } $canvas->pack(); $self->{'MainWindow'} = $main; $self->{'Canvas'} = $canvas; $self->{'Views'} = \%views; $self->{'Signal'} = $signal; bless $self; } sub next { my ($self) = shift; my $state = $self->{'Signal'}->{'State'}; my $view = $self->{'Views'}->{$state}; my $canvas = $self->{'Canvas'}; $canvas->itemconfigure($view,-fill=>'black'); $state = $self->{'Signal'}->next(); $view = $self->{'Views'}->{$state}; $canvas->itemconfigure($view,-fill=>$state); } sub update { my ($self) = shift; $self->{'MainWindow'}->update(); } package Main; $signal = SignalView->new(); $signal->update(); while($i++<100) { $signal->next(); $signal->{'Signal'}->print(); print "\n"; $signal->update(); sleep(1); }この方法では、信号は自動的に点滅するという感じではない。そのためには、 after という機能を使うことができる。
package MainWindow; sub Signal { my ($self) = shift; return SignalView->new(-main=>$self); } package SignalView; sub new { my $type = shift; my %params = @_; my $self = {}; my $signal = Signal->new(%params); my $main = $params{-main}; my $canvas; my $states = $signal->{'States'}; my %views = (); $self->{'Interval'} = 500; $canvas = $main->Canvas(-width=>50,-height=>150); $x = 5; $y = 5; $r = 40; for $state ( @$states ) { $views{$state} = $canvas->create('oval', $x, $y, $x+$r, $y+$r, -width => 1, -outline => 'black', -fill => 'black'); $y += 50; } $canvas->pack(-side=>left); $self->{'MainWindow'} = $main; $self->{'Canvas'} = $canvas; $self->{'Views'} = \%views; $self->{'Signal'} = $signal; bless $self; } sub next { my ($self) = shift; my $state = $self->{'Signal'}->{'State'}; my $view = $self->{'Views'}->{$state}; my $canvas = $self->{'Canvas'}; $canvas->itemconfigure($view,-fill=>'black'); $state = $self->{'Signal'}->next(); $view = $self->{'Views'}->{$state}; $canvas->itemconfigure($view,-fill=>$state); } sub update { my ($self) = shift; $self->{'MainWindow'}->update(); } sub run { my ($self) = shift; my %params = @_; my $main=$self->{'MainWindow'}; if(defined($params{'Interval'})) { $self->{'Interval'} = $params{'Interval'}; } if(! $self->{'Running'}) { $self->{'Running'} = 1; $main->after($self->{'Interval'},sub {$self->running()}); } } sub running { my ($self) = shift; my $interval = $self->{'Interval'}; if($self->{'Running'}) { $self->next(); $self->update(); $self->{'MainWindow'}->after($interval,sub {$self->running()}); } } sub stop { my ($self) = shift; $self->{'Running'} = 0; } package Main; use Tk; $main = MainWindow->new(); $signal = $main->Signal(); $signal->run(); &MainLoop();
この信号をさらに外から制御するには、controller をViewに付加する。
ボタンからの情報を処理するにはCall Backを使う。Call Back では、
Call Back する先を、my 変数により保持する。これをClosure という。
オブジェクトを使っても同じことが実現できるが、Closureの方が
構文的には軽い。
package SingalView; sub controller { my ($self) = shift; my $main=$self->{'MainWindow'}; # my $c= $main->Toplevel(); my $c= $main->Frame(); my $int= $c->Frame(); my $int_entry = $int->Entry(-relief => 'sunken',-width=>5); my $int_label= $int->Label(-text=>"Interval:"); my $quit = $c->Button( -text => 'Quit', -width => 10, -command => sub {$main->destroy}); my $run = $c->Button( -text => 'Run', -width => 10, -command => sub { $self->run(Interval=>($int_entry->get())) } ); my $stop = $c->Button( -text => 'Stop', -width => 10, -command => sub {$self->stop}); $int->pack(-side => 'top', -fill => 'x'); $int_entry->pack(-side => 'right'); $int_label->pack(-side => 'left'); $c->pack( -side => 'right', -expand => 'no', -pady => 2); $int->pack(-side => 'top', -expand => 'no', -pady => 2); $run->pack( -side => 'top', -expand => 'no', -pady => 2); $stop->pack(-side => 'top', -expand => 'no', -pady => 2); $quit->pack(-side => 'top', -expand => 'no', -pady => 2); # set default value $int_entry->insert(0,$self->{'Interval'}); $self->{'Controller'}=$c; $self->update(); } # end
電子レンジの振る舞いを表すGUIをPerlで記述してみよ
解答は、 kono@ie.u-ryukyu.ac.jp まで、来週までにメールで送ること。 Subject には、
Report on Software Engineering Lecture 6/15を付けること。