Software Engineering Lecture 6/29
Menu Menu
先週の復習
- シーケンス図と協調図
- Perl/Tk
状態図
協調図は、実際に行われる動作の一部を表すに過ぎない。より完全なオブジェクトの動作は、状態モデルによって表される。
状態モデル
オブジェクトは、いつも同じような動作をするわけではない。あるメッセージを受け取った後には、異なる振る舞いをするのが普通である。例えば、- 電子レンジの扉を開ける
- 食品を入れる
- 扉を閉める
- スィッチを入れる
- 加熱中
- 加熱終了
- 扉を開ける
- 食品を出す
- 扉を閉める
しかし、オブジェクトはまったく予想できない動作をするわけではない。この場合でも、この一連の動作は一種のループになっていて、ループの各点では同じ動作をすると期待される。このようなループがオブジェクトライフサイクルを構成する。
「扉を閉める」「スィッチ」などがオブジェクトの状態を変えるきっかけである。これらを「イベント」と呼ぶのが普通である。オブジェクトの状態はイベントによって変わる、あるいは、オブジェクトの状態を変えるものがイベントである。
この(個々の)オブジェクトの振る舞いを表すのには、状態遷移図を使うのが普通である。例えば、一番簡単な例として電灯の状態遷移図を書いてみよう。
問題
電子レンジの振る舞いを表す状態遷移図を記述してみよ
Perl での状態記述
Perl で状態遷移を記述するにはどうすれば良いか? 緑、黄、赤と順に変化する信号機を Perlのプログラム で記述してみよう。
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 の状態を持つ。したがって、個々のオブジェクトの状態だけでなく、システム全体の振る舞いを見通し良くするための表現が必要だと考えられる。それには協調図などを用いることになる。
信号機を表示するPerl/Tk のプログラム
信号機を表示する 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();
Call Back と Controller
この信号をさらに外から制御するには、controller をViewに付加する。 View を含んだPerl/Tkのプログラム
ボタンからの情報を処理するには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
問題
この信号の実装を表す概念モデルを作成せよ。信号の振舞を記述する協調図とシーケンス図を作成せよ。
先週の宿題
電子レンジの振る舞いを表す状態遷移をPerlで記述してみよ。また、Perl/Tk による GUI を付けよ。
ソフトウェア・パターン
オブジェクトは、このようなオブジェクトの組み合わせ(Object Composition) で作られていく。これを、一つの知識体系としてまとめたものの一つが、デザイン・パターンあるいは、ソフトウェア・パターンと呼ばれるものである。オブジェクト指向プログラミングでは、GoF パターンと呼ばれるものが有名である。(Gang of Four)状態遷移の作り方は一つのデザイン・パターンである。
ソフトウェア・パターンの意味
パターン・コミュニティ
GoF パターン
オブジェクト作成のパターン
Abstract FactoryBuiler
Factory Method
Prototype
Singleton