Software Engineering Lecture s8

Menu Menu


先週の復習


GUI / Real-Time Programming のパターン

GUI / Real-Time Programming では、決まった入力やファイルを処理するわけではなく、さまざまな実時間(Real-Time) のイベントを処理する必要がある。この手法には、大きく分けて2種類のパターンがある。

  1. イベントループ
  2. コールバック


イベントループ

一つ一つイベントを取り出し、それを条件判断して、処理するサブルーチンを呼び出す。通常は、以下のようなメインループがある。

  main() {
   event e;
   ...
   while(e = next_event()) {
      subroutine1(event e);
      subroutine2(event e);
       ...

サブルーチンは、以下のような形になる。

  state substate1;
 
  subroutine1(event e) {
   if (event==SOME_EVENT) {
   if (state.part=SOME_STATE) {
       state.part = NEW_STAE;
   ....
      

X Window System の基本的なライブラリである Xlib は、このような形式となっている。


コールバック

イベントを受け取り、各オブジェクトに配るスケジューラが一つ存在し、それに各オブジェクトがコールバックと呼ばれるサブルーチンを登録する。

イベントに応じてスケジューラがコールバックルーチンを呼び出す。

Perl/Tk は、このような形式となっている。GUI Toolkit のほとんどは、このような形式である。Perl/Tk は、Xlib の上に構築されている。

スケジューラは、イベントを受け取るイベントループ(main_loop)を持っているのが普通である。これは、スレッドで実現しても良い。

   main() {
      s = event_scheduler();
      create_object1(s);
      create_object2(s);
        ...
      s->main_loop();
  }

オブジェクト側では、コールバックルーチンを登録すれば良い。

  static call_back(object self,call_back_arg a) {
     if (a->event==MOUSE_MOVE) {
         self->position++;
        ...
  }
  create_object1(scheduler s) {
     event_mask = MOUSE_MOVE | BUTTON_DOWN;
     s->rigister(event_mask,call_back,self);
   ...
  }


状態図

イベントループでも、コールバックでも、イベントによって生じるのは、オブジェクトの状態変更である。これを記述する方法を習得することが重要である。

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


状態モデル

オブジェクトは、いつも同じような動作をするわけではない。あるメッセージを受け取った後には、異なる振る舞いをするのが普通である。例えば、
  1. 電子レンジの扉を開ける
  2. 食品を入れる
  3. 扉を閉める
  4. スィッチを入れる
  5. 加熱中
  6. 加熱終了
  7. 扉を開ける
  8. 食品を出す
  9. 扉を閉める
という操作を考えよう。この時、同じ「スィッチを入れる」という操作でも扉が開いている時には、加熱が始まってしまってはいけない。関数型言語では本当は、そういうことはない。同じ入力ならば、同じ出力を返すのが関数なはずである。このように、同じ入力でも違った動作をすることを、オブジェクトが状態を持つという。

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

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

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


問題8.1

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

デザインパターンのステートパターンと、javax.swing を用いて、電子レンジのGUI表示を実現せよ。


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


問題8.5

この信号の実装を表す概念モデルを作成せよ。信号の振舞を記述する協調図とシーケンス図を作成せよ。


問題8.6

電子レンジの振る舞いを表す状態遷移をPerlで記述してみよ。また、Perl/Tk による GUI を付けよ。


おまけ

状態遷移、SPIN、時相論理、ITL

Shinji KONO / Tue Jun 26 14:18:37 2007