Software Engineering Lecture 6/5

Menu Menu


復習


情報モデル

情報モデルを思い出そう。これはオブジェクトの相互の参照(Reference)を表している。さらに、これは個別のインスタンスを表しているのではなく、共通の性質を持ったオブジェクトの集合(class)の関係を表していることに注意しよう。従って、オブジェクトの静的な関係を表していると考えられる。


1対1, 多対1関係

これらは通常はインスタンス変数に相手のオブジェクトを格納することによって実現される。多対1の場合は、リストにして格納する。リストに対する操作、push, pop, delete などを思い出すこと。splice を使うとより細かい操作が可能になる。関係は方向性があるので矢印を使って表すこと。複数の関係先は矢印を二重にする。


継承関係 is-a

これは、オブジェクト指向言語の継承の機能を使って実現される。もし継承の機能がなければ、手動でソースをコピーしても構わないし、インスタンスを別に作って、そこにメッセージを再転送して実現しても良い。これは委譲 delegationと呼ばれる。動的に委譲先を変えることができるので委譲の方がより細かい操作が可能になる。図では矢印に棒線をはさんで表す。


part-of

委譲を採用するなら、委譲先は複数あっても良い。この場合は、委譲先の性質を引き継ぐというよりは、機能を部品に分割するということになる。このような関係は part-fo と呼ばれる。表記は、is-a と同じである。

シュライアー・メラーのオブジェクトライフサイクルでは、オブジェクトの動的な関係は、状態モデルとプロセスモデルによって表される。




状態モデル

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

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

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

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




問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には以下のような図を書くことができる。
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();

この信号をさらに外から制御するには、controller をViewに付加する。

    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で記述してみよ

lassをWindowに表示するプログラムをPerl/Tkで記述して見よ。Perl/Tkに関しては、/usr/libdata/perl5/site_perl/Tk とPerl/Tkの例題を参照すること。 解答は、kono@ie.u-ryukyu.ac.jp まで、来週までにメールで送ること。Subject には、

   Report on Software Engineering Lecture 6/2 

を付けること。




Shinji KONO / Tue Jun 2 17:57:37 1998