Software Engineering Lecture 6/15

先週の復習

状態図

協調図は、実際に行われる動作の一部を表すに過ぎない。より完全な オブジェクトの動作は、状態モデルによって表される。



状態モデル

オブジェクトは、いつも同じような動作をするわけではない。あるメッセージを 受け取った後には、異なる振る舞いをするのが普通である。例えば、 という操作を考えよう。この時、同じ「スィッチを入れる」という操作でも 扉が開いている時には、加熱が始まってしまってはいけない。関数型言語では 本当は、そういうことはない。同じ入力ならば、同じ出力を返すのが関数 なはずである。このように、同じ入力でも違った動作をすることを、オブジェクト が状態を持つという。

しかし、オブジェクトはまったく予想できない動作をするわけではない。この 場合でも、この一連の動作は一種のループになっていて、ループの各点では 同じ動作をすると期待される。このようなループがオブジェクトライフサイクル を構成する。

「扉を閉める」「スィッチ」などがオブジェクトの状態を変えるきっかけ である。これらを「イベント」と呼ぶのが普通である。オブジェクトの状態は イベントによって変わる、あるいは、オブジェクトの状態を変えるものが イベントである。

この(個々の)オブジェクトの振る舞いを表すのには、状態遷移図を使うのが 普通である。例えば、一番簡単な例として電灯の状態遷移図を書いてみよう。





問1

電子レンジの振る舞いを表す状態遷移図を記述してみよ

例題

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

電子レンジの振る舞いを表す状態遷移をPerlで記述してみよ



システムの動的振る舞い

オブジェクトは、個々に勝手に動作するのではなく、全体としてある目的を もって協調して動作するべきであろう。個々のオブジェクトの振る舞いの 総和がシステムの振る舞いを決める。これは単純には、個々のオブジェクトの 状態遷移図の積であるはずである。

しかし、それは人間の手に負えないほど複雑なものであるのが普通である。 たとえば、2状態のオブジェクトが10個あったとすると、その積は2^10=1024 の状態を持つ。 したがって、個々のオブジェクトの状態だけでなく、システム全体の振る舞いを 見通し良くするための表現が必要だと考えられる。それには協調図などを 用いることになる。


Perl/Tkによる記述

Perl/Tk では、MainWindow を作り、そこに、いろいろなwidgetを pack していく。
	my $main = MainWindow->new();
とすれば、MainWindowができる。MainWindowのインスタンスとして、いろいろな ものを使うことができる。
Label文字を印として置く
Text編集用のテキスト
Entry入力カラム
Canvas図形表示
Frameいくつかのwidgetをまとめる


Canvas とタグ

Canvasには以下のような図を書くことができる。
oval楕円
rectangle
line
polygon多角形
bitmapイメージ
textテキスト
これらは、CanvasにタグやIDで指定することによって移動したり変形したり することができる。
$canv->move($ball, $deltax, $deltay);移動
$canvas->itemconfigure($view,-fill=>'black'); 色などの変更
$canvas->delete($view); 削除
これらに関しては man canvas でもだいたいのことを知ることができる。

信号機を表示する 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に付加する。

ボタンからの情報を処理するには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 
を付けること。