ObjectSquare [2003 年 3 月号]

[技術講座]


Java ではじめる UML

[第3回] 相互作用図

林俊樹/森三貴/山内亨和/高木栄児/山口健/伊藤喜一

 今回は、構成要素がいかに振る舞うのかを表現するために使用される 「相互作用図」として、「シーケンス図」、「コラボレーション図」を取り上げます。 ケース・スタディのテーマであるスケージュラ・ソフトのソース・コードを基に、 シーケンス図とコラボレーション図の作成に挑戦してみましょう。


1. 前回のおさらい

 まず初めに、前回の内容を簡単におさらいしましょう。 前回はシステムの論理的な ( 静的な ) 構造をきちんと把握するために、 既存のソース・コードを基に、クラス図とパッケージ図を書き上げました。 こうやって図に書き出すことにより各クラス間の関係が一目瞭然になり、 いちいちソース・コードを追いかけるよりも、 このように図による表現のほうが理解し易いことがわかりました。 スケジューラの論理構造が把握できた Chen 君は、 次にプログラムの振る舞い ( 機能 ) がどう実現されているかを調べてみることにしました。

 Chen 君の発言
「クラス図とパッケージ図を書いたから、次はプログラムの機能がどうやって実現されてるかを把握する必要があるなぁ・・・。 よし、とりあえず予定を入力する機能のシーケンス図を書いてみるか。」

2. 相互作用図

 相互作用図は、プログラム上のある振る舞いで、オブジェクト群がどのように協調動作 ( コラボレーション ) するのかを表現するための図です。実際に表現する手段として、「シーケンス図」と「コラボレーション図」の2種類の図があります。 それでは、これら両方の図の表記法について説明します。

2.1 シーケンス図の表記法

 シーケンス図はオブジェクトの相互作用をを時間軸にそった形式で表現します。縦軸は時間を表し、 横軸は個々のオブジェクトを示します。では、Chen 君が着手した予定を入力する機能について、 私たちも実際にソース・コードを基にシーケンス図を書いてみることにしましょう。

 まず、予定を入力する相互作用がどこから始まっているかを探してみましょう。 画面の操作上は、メイン画面 ( 画面 1 ) で、まだ何もスケジュールが入力されていない白色のセルを ユーザーが押下した場合 ( 新規入力 ) と、予定を追加/編集/削除する画面 ( 画面 2 ) でユーザーが [ 追加 ] ボタンを押下した場合に、 予定を新規入力する画面 ( 画面 3 ) が現れます。そして、画面 3 で時間とスケジュールを入力し、 [ 登録 ] ボタンを押下した場合に予定の入力ができるようになっています。 ソース・コードを調べてみると、クラス RegisterDialog のメソッド registerButton_antionPerformed ( リスト 1 ) が、 そのコードになっているようです。

 
スケジューラのメイン画面 予定を追加/編集/削除する画面
画面 1 : スケジューラのメイン画面 画面 2 : 予定を追加/編集/削除する画面

 新規の予定を登録する画面
画面 3 : 新規の予定を登録する画面

リスト 1 :クラスRegisterDialog ( RegisterDialog.java。一部抜粋 )

public class RegisterDialog extends JDialog
{
  private CalendarDate  calendarDate;
  private FixedSchedule fixedSchedule;
  …略…
  void registerButton_actionPerformed(ActionEvent e) {
Date date = calendarDate.getDate(); − ( 1 )
String sh = (String)hoursComboBox.getSelectedItem(); String sm = (String)minutesComboBox.getSelectedItem(); int hour = Integer.parseInt(sh); int minute = Integer.parseInt(sm); Time time = DateTimeUtils.toSQLTime(hour, minute, 0); String memo = memoTextField.getText();
FixedSchedule newSchedule = new FixedSchedule(date, time, memo); − ( 2 )
if (fixedSchedule != null) { − ( 3 ) calendarDate.removeFixedSchedule(fixedSchedule); }
calendarDate.addFixedSchedule(newSchedule); − ( 4 )
dispose(); } }

 この部分からシーケンス図を記述することにしましょう。まず最初に、横軸にオブジェクトを配置していきます。 並ぶ順番には特に決まりはないので、相互作用の流れが判り易いように配置してください。 一般的には左から右へ呼び出しが進んでいくように配置します。 相互作用に参加するオブジェクトは長方形で表します。長方形の中身は「分類子ロール ( ClassifierRole ) 」を記述します。 これはオブジェクトのロール名とクラス名に下線を引いた形式で構成されたものです。以下のように記述します。


ロール名 : クラス名
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄

 ここでは、クラス CalendarDate を配置してみます( 図 1a )。ロール名とクラス名は、どちらも省略可能です。 クラス名だけを記述する場合は、図 1b のようにロール名のみの表記と区別するために分類子名の前に" : ( コロン ) "をつけます。 また、分類子名が曖昧にならない場合は、図のようにパッケージ名を省略できます。

 
分類子ロールの例 ロール名を省略した分類子
図 1a : 分類子ロールの例 図 1b : ロール名を省略した分類子

 パッケージ名を含む場合は、以下のようにパッケージ名を " :: ( 2 重コロン ) " で区切ります。


calendarDate : sucheduler :: domain :: CalendarDate
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄

 オブジェクトから下に引かれている点線は「生存線 ( lifeline ) 」と呼びます。その名の通り、オブジェクトが存在する期間を表すものです。 シーケンス図の中で生成/消滅するオブジェクトで無い場合は、生存線は図の一番上から一番下まで描かれます。 同じ要領で、リスト 1 - ( 1 ) の部分の calendarDate をオブジェクトとして配置しましょう。 次に、getDate( ) メソッドを呼び出している部分を「メッセージ ( Message ) 」として オブジェクト間に閉実線の矢尻で矢印を記述します ( 図 2 )。 UML 1.4 では矢印は 3 種類存在します ( 矢印の種類とその意味については表 1 をご参照ください ) 。

メッセージの例
図 2 : メッセージの例


形状 意味
閉実線矢じり 閉実線矢じり 手続き呼び出しの制御フロー。呼び出し側は送信したメッセージの処理が終わるまで待たなければなりません。
開き矢じり 開き矢じり 非同期制御フロー。呼び出し側は相手の処理終了を待たずに次のステップに移行します。
破線開き矢じり 破線開き矢じり 呼び出しからのリターン。手続き的な制御フローの場合は省略可能です。

表 1 : メッセージに使用する矢印の種類

 またメッセージのラベルには、呼び出す操作の名前などを記述することができます。たとえば、以下のように記述します。


シーケンス番号 : 戻り値 : = 操作名( パラメータ )

 「シーケンス番号 ( sequenc e number ) 」は、メッセージの相対位置を示すものです。 シーケンス図ではメッセージが時系列にそって上から順番に配置されるため省略可能です。 「戻り値」、「操作名」、「パラメータ」については Java と同様の形式で記述することができます。 生存線にそって存在する矩形は「活性区間 ( activation ) 」と呼ばれます。 オブジェクトがメッセージを受信して操作を実行している期間を表します。 また、後述する自己呼び出しの場合は活性区間が重なって表現されます。 活性区間は Java 仮想マシンのスタック・フレームに相当します。 ソース・コードの次の箇所は、GUI の入力項目から必要な情報を取得/変換しているだけで、 この相互作用の中では重要な処理ではないので、シーケンス図には記述しません。 リスト 1 - ( 2 ) では、スケジュールに追加するクラス FixedSchedule の新しいインスタンスを生成しています。 オブジェクトを生成する場合は、図 3 のようにオブジェクトに対して生成のメッセージを送信します。 このとき、生存線は生成された箇所から始まっていることに注意してください。 またここでは、メッセージのラベルとしてコンストラクタを使用していますが、 代わりにステレオタイプとして << create >> を使用することもできます。

生成のメッセージの例
図 3 : 生成のメッセージの例


図 4 : ガード条件の例

 次の、リスト 1 - ( 3 ) は条件分岐が存在します。 この条件分岐はメッセージのラベルに「ガード条件」を記述することにより表現することができます。 ガード条件は" [ ] " で囲まれた式が真の場合にだけ、メッセージを送信するという意味で使用されます。 ガード条件内の書式は UML の仕様では特に定義されてないので、 Java の条件式と同じように " [ fixedSchedule != null ] "などといった表現が可能です ( 図 4 ) 。 クラス CalendarDate のメソッド removeFixedSchedule の詳細を見てみましょう ( リスト 2 )。

リスト 2 : クラス CalendarDate ( CalendarDate.java。一部抜粋 )

public class CalendarDate
{
  private Scheduler scheduler;
  private Date date;
  private int dayOfMonth;
  private int dayOfWeek;

  // List < Schedule >
  private List    schedules    = null;
  private Boolean hasSchedules = null;
  …略…
  public void removeFixedSchedule(FixedSchedule schedule) {
if (!schedule.isValid(date)) { − ( 1 ) throw new IllegalArgumentException(schedule.toString()); }
scheduler.removeFixedSchedule(schedule); − ( 2 )
if (schedules == null) { − ( 3 ) hasSchedules = null; return; }
// キャッシュを更新する。 − ( 4 ) int index = Collections.binarySearch(schedules, schedule, scheduleCmp); if (index >= 0) { schedules.remove(index); if (schedules.isEmpty()) hasSchedules = Boolean.FALSE; }
} …略… }

 まずリスト 2 - ( 1 ) で、渡された引数が適切かどうかの判定を行っています。 このシーケンスでは、適切なインスタンスを渡しているはずなので、この部分は省略します。 リスト 2 - ( 2 ) は、クラス CalendarDate のフィールド scheduler のメソッドを呼び出しています。 先程と同じ要領でシーケンス図にオブジェクト scheduler とメッセージ配置しましょう。

 リスト 3 はクラス Scheduler のソースです。

リスト 3 :クラス Scheduler ( Scheduler.java。一部抜粋 )。

public class Scheduler
{
  // SortedMap &qt Date, ScheduleList >
  private SortedMap      fixedSchedules  = new TreeMap();

  …略…
  
  public ScheduleList getFixedSchedules(Date date) {
ScheduleList list = (ScheduleList)fixedSchedules.get(date); − ( 2 ) if (list == null) { fixedSchedules.put(date.clone(), list = new ScheduleList()); } return list; }
…略… public void removeFixedSchedule(FixedSchedule schedule) { Date date = schedule.getDate();
getFixedSchedules(date).remove(schedule); − ( 1 )
} …略… }

 リスト 3 - ( 1 ) は自分自身のメソッドを呼び出しています。 これは「自己呼び出し ( self - call ) 」形式の呼び出しで表記法図 5 のようになります。 自分自身にメッセージが送信され、活性区間がもう一つできているのが解りますね。

自己呼び出しの例
図 5 : 自己呼び出しの例                       

 自己呼び出し中の活性区間はリスト 3 - ( 2 ) と対応付けられます。 ソースの内容はクラス SortedMap に問い合わせてクラス ScheduleList のインスタンスを取得し、 date に対応するクラス ScheduleList のインスタンスが存在しない場合は、 新たに作成してそのインスタンスを返すという振る舞いです。 さしあたって、この相互作用では重要ではないので省略します。

 再びリスト 3 - ( 1 ) に戻って、自己呼び出しによって取得したクラス ScheduleList のインスタンスにメッセージを送ります。 オブジェクト fixedSchedl eはどこからも参照されずオブジェクトの消滅として扱われるため、 消滅のメッセージをオブジェクト fixedSchedule に送信します。 その場合はメッセージのラベルにステレオタイプ << destroy >> を記述 ( 省略可 ) し、 消滅するオブジェクトに大きな×印を記述します ( 図 7 )。 ×印以降の生存線は記述しないところに注意してください。 これで removeFixedSchedule メソッドの振る舞いは完了しました。 シーケンス図では Java の return に対応するメッセージがあり破線開き矢尻の矢印で記述できます ( 図 6 )。 もっとも、メソッド呼び出しのような手続き的な制御フローでは、 終了した場合に、呼び出し側に制御が戻ることが明白なため矢印は省略することが可能です。 矢印が多いと図が把握しづらくなるため、省略する傾向にあります。 リスト 1 - ( 4 ) のフローも同じ要領でシーケンス図として書き出しましょう。 完成した図は図 8 のようになります。 この図ではすべてのメッセージにリターンの矢印を記述してありますが、 実際に記述する場合は省略して構いません。 一般的には、呼び出してすぐリターンするメッセージや自己呼び出しに関しては、 簡略化のためリターンの矢印は省略します。

 
リターンの例
図 6 : リターンの例 図 7 : オブジェクトの消滅の例

シーケンス図の完成図
図 8 : シーケンス図の完成図

 Chen 君の発言
「よし、予定を入力する機能がどう実現されているかがだいたいわかったぞ。この調子でほかの機能のシーケンス図も書いてみよう」

 紙幅の都合上、ここでは他のシーケンス図は割愛します。他のシーケンス図については、是非読者自身でチャレンジしてみてください。

 参考までに、その他の表記法として「分岐 ( branching ) 」と「反復 ( iteration ) 」があります。 例えば以下のようなソース・コードをシーケンス図で表現したい場合は、 分岐を使用して図 9 のように表記します ( 分岐に関する表記以外は省略しています )。


public class SchedulerDAOImpl implements SchedulerDAO
	{
  …略…
  protected void loadSchedule(Scheduler scheduler, List values) {
    String kind = (String)values.get(FIELD_KIND);
    if (kind.equals(KIND_FIXED)) {
      scheduler.addFixedSchedule(toFixedSchedule(values));
    } else if (kind.equals(KIND_WEEKLY)) {
      scheduler.addWeeklySchedule(toWeeklySchedule(values));
    …略…
  }
  …略…
}

 分岐の例
図 9 : 分岐の例

 以下のようなループを使用したソース・コードでは、反復を利用して図 10 のように表記します ( 反復に関する表記以外は省略しています )。


public class Scheduler
{
  public void clearFixedSchedules() {
    for (Iterator i = fixedSchedules.values().iterator(); i.hasNext(); ) {
      ScheduleList list = (ScheduleList)i.next();
      list.clear();
    }
    fixedSchedules.clear();
  }
  …略…
}

 反復の例
図 10 : 反復の例

2.2 コラボレーション図の表記法

 コラボレーション図はオブジェクトの相互作用を役割とリンクの形式で表現します( *1 )。 コラボレーション図はオブジェクトとリンクの集まりで構成されています。 図 11 は、図 8 の内容をコラボレーション図で表現したものです。 以下、シーケンス図と異なる特徴を中心に説明します。

コラボレーション図
図 11 : コラボレーション図

 シーケンス図との違いは、時間次元を扱わないためシーケンス番号が必須項目であることです。 また、シーケンス番号はある操作中で操作を呼び出していることが明確に解るように 3.1.1 のように小数表現になっています。 図 8 のシーケンス図と見比べて、コラボレーション図のシーケンス番号のどこが小数表現になっているかを調べてみると良いでしょう。 次に、オブジェクト newSchedule : FixedSchedule に注目してください。 { new } というキーワードが記述されています。 UML では " { } " で囲まれた文字列を「制約 ( Constraint ) 」と呼んでいます。 シーケンス図のように生存線がないため、オブジェクトが相互作用の実行中で生成されるのか、 もしくは削除されるのかを示すために、コラボレーション図ではこのような制約を記述します。 生成される場合は { new } 、削除される場合は { destroyed } 、生成および削除される場合は { transient } をそれぞれ用います。 コラボレーション図でのリンクはクラス図上での関連を示します。 Java のプログラム上では、引数やローカル変数、および自己呼び出しを用いることがあり、 これらはクラス図上の関連とは異なるものです。このようなリンクは一時リンクとして他のリンクと区別する必要があります。 そのため、コラボレーション図では一時リンクに << parameter >>、 << local >>、 << self >> といったステレオタイプを付けて通常のリンクと区別することができます。 メッセージの記述は、リンクに付加されたラベル付きの矢印で示します。 メッセージのラベルに関しては、シーケンス図との違いはありません。 また、矢印の種類に関してもシーケンス図と同じ種類の矢印が使用できます。

2.3 相互作用図の使い分け

 最後に相互作用図の使い分けについて説明します。 シーケンス図とコラボレーション図は、両方ともに表現しようとする内容に共通性があり、 それぞれの図に相互変換が可能です。 ひとつの相互作用に対しては、どちらかの図がひとつあれば充分な場合がほどんどです。 シーケンス図は時間軸によってメッセージの順序を容易に把握できるので、 複雑なシナリオを表現する場合は、コラボレーション図よりも適しています。 逆に、コラボレーション図はオブジェクト間の繋がりが容易に把握できるので、 オブジェクト間の関連を表現する場合は、シーケンス図よりも適しています ( *2 )。 どちらの形式を用いて相互作用を表現するかは、「その図で明確に表現したいことは何か」に応じて判断すると良いでしょう。


3. まとめ

 相互作用図を書き終えた Chen 君のもとに Jun 先輩が様子をうかがいにやってきました。

Jun 先輩の発言
「やあ、調子はどうだい?」
Chen 君の発言
「えーと、プログラムの振る舞いを把握するために相互作用図を書いてみました。こんな感じでどうでしょう?」
Jun 先輩の発言
「じゃあ、ちょっと見せてもらえるかな・・・。ふーん、ソース・コードを追いかけずにひと目で機能を実現する方法が解るところが良いね。 これなら、Java を知らない他の開発者が見ても、概要を理解できるかもね。じゃあ、引き続きよろしく。」

 今回は、スケジューラのソース・コードをもとに、プログラムの相互作用を表現するシーケンス図とパッケージ図について説明しました。

 次回は振る舞いに関する UML ダイアグラムの続きとしてステートチャート図とアクティビティ図について説明する予定です。


コラム

相互作用図をどこまで詳細に記述すべきか?

 相互作用図の記述に慣れないうちは、相互作用図をどの程度まで詳細に記述する必要があるかについて悩みがちです。 例えば、相互作用に関係するオブジェクトはすべて記述した方が良いのか、 条件分岐の多い相互作用はどこまで表現すべきか、などです。 結論から言うと、あまり詳細に書く必要はありません。 相互作用図のメリットは、あくまでもオブジェクトの協調動作の流れや関係を"ひと目"で把握できることです。 むしろ、重要でないオブジェクトやメッセージは省略したり、条件分岐が多い場合はシナリオ毎に 複数の相互作用図に分けるなどして、重要な部分が容易に理解できるように、 なるべくシンプルにした方が良いでしょう。




《参考文献》

※この記事は、『Java World』 2002 年 10 月号に掲載された 「Java で学ぼう ! 初めての UML」に一部加筆・追加したものです。


連載記事一覧

関連記事一覧


© 2002 - 2003 OGIS-RI Co., Ltd.
Prev. Index Next
Prev. Index Next