![]() |
[2003 年 2 月号] |
[技術講座]
本記事で使用しているソースコード(スケジューラ)の完全版がダウンロードできます。ご活用ください。
まず最初に、前回提示したケース・スタディを簡単におさらいしておきたいと思います。
ある中堅ソフトウェア会社に勤める Chen 君は、リーダーの Jun 先輩から、チームで使用しているスケジューラのドキュメント作成と機能追加を依頼されました。これは、転勤してしまった Mika さんが、以前開発したものです。画面 1 がそのスケジューラのメイン画面です。
画面 1 : スケジューラのメイン画面
![]() |
「スケジューラの機能はだいたいわかったから、今度はソースコードを調べてみよう。えーと、ファイルの構成は・・・ ( リスト 1 )。なるほど、ちゃんとパッケージに整理はされているけど、思ったよりクラス数が多いな。まずは、クラス図をかいて全体の構成をつかむ必要がありそうだ。 domain パッケージあたりから手をつけるといいかな ? 」 |
---|
- scheduler
- Main.java
- dao
- SchedulerDAOImpl.java
- domain
- CalendarDate.java
- CalendarMonth.java
- Content.java
- FixedSchedule.java
- RepeatedScheduler.java
- Schedule.java
- ScheduleList.java
- Scheduler.java
- SchedulerDAO.java
- WeeklySchedule.java
- gui
- CalendarFrame.java
- DateCell.java
- DateCellRenderer.java
- DateScheduleDialog.java
- MonthTable.java
- MonthTableModel.java
- RegisterDialog.java
- util
- AppUtils.java
- DateTimeFormat.java
- DateTimeUtils.java
ということで、 domain パッケージの各クラスのソースコードを見ながらクラス図の説明をしていきたいと思います。まずは、一番シンプルな Content クラス ( リスト 2 ) で基本的な部分を押さえましょう。
package scheduler.domain;
/** スケジュールの内容. */
public class Content {
private String memo;
public Content(String memo) {
this.memo = memo;
}
public String getMemo() {
return memo;
}
public void setMemo(String value) {
memo = value;
}
public String toString() {
return memo;
}
}
それでは早速始めます。まず、 Java の「クラス」は UML の「クラス ( class ) 」にそのまま対応します。 UML でクラスは図 1 のように、標準で 3 つの区画を持った実線の長方形で表します。クラス名は一番上の区画の中央に太字で記入します。
![]() |
図 1 : Content クラス |
Java の「フィールド」を UML で表す方法はいくつかありますが、一番基本的なものは「属性 ( attribute ) 」で表す方法です。次の書式で二番目の区画に記入します。
可視性 属性名 : 型 = 初期値
名前と型の順番が Java の文法とは逆になっているので注意してください( *2 )。可視性 ( アクセス制御 ) の表し方は表 1 のとおりです。
記号 | Java の修飾子 | 意味 |
---|---|---|
+ | public | すべてのクラスからアクセス可能。 |
# | protected | サブクラスまたは同一パッケージのクラスからのみアクセス可能。 |
~ | ( 指定なし ) | 同一パッケージのクラスからのみアクセス可能。( UML 1.4 から) |
- | private | そのクラス自身からのみアクセス可能。 |
Java の「メソッド」は UML の「操作 ( operation ) 」に対応します。次の書式で 3 番目の区画に記入します。
可視性 操作名 ( パラメータ ) : 戻り値
やはり名前と型の順番が Java の文法とは逆になっているので注意してください(*2 )。可視性の表し方は属性と同じです。
いかがですか ? それほど難しくありませんよね。なお、クラスの標準的な表記法は図 1 のとおりですが、そのモデルで特に注目しない要素は適宜省略してよいことになっています。図 2 では、属性の可視性と型、操作の可視性とシグニチャ ( パラメータと戻り値 ) を省略しています。図 3 では属性、操作を区画ごと省略してしまいました。表示する要素を選択することで、そのモデルで表したいことを強調することができます。
![]() |
![]() |
図 2 : 可視性、シグニチャを省略した Content クラス | 図 3 : 属性、操作区画を省略した Content クラス |
クラスの表記法について、何点か補足しておきたいと思います。
public abstract class Schedule {
...
}
まず、このように abstract をつけて宣言される抽象クラスですが、 UML ではクラス名を斜体にして表します ( 図 4 )。また、フォントの変更だけでわかりにくい場合は、クラス名の後に { abstract } というプロパティをつけて表すこともできます ( 図 5 )。
![]() |
![]() |
図 4 : 抽象クラス | 図 5 : プロパティを使用した抽象クラス |
public interface SchedulerDAO {
public void init();
public void load(Scheduler scheduler);
public void save(Scheduler scheduler);
}
もう一つ、このようにメソッドの宣言だけされ、実装を持たない、インタフェース ( interface ) ですが、 UML ではクラスと同じ長方形を用い、インタフェース名の上にキーワード << interface >> をつけて表します ( 図 6 )。操作を特に明示する必要がない場合は、図 7 のようなロリポップ ( *4 ) と呼ばれるアイコンで表すこともできます。
![]() |
![]() |
図 6 : インタフェース | 図 7 : アイコンで表示されたインタフェース |
抽象クラスとインタフェースの説明をしたので、継承 ( 拡張 : extends ) と実装 ( implements ) の表記法も紹介しておきましょう。
public class FixedSchedule extends Schedule {
...
}
Java の継承は UML の汎化 ( generalization ) に対応します。子クラスから親クラスへ実線を引き、先端に△をつけて表します( 図 8 )。
![]() |
図 8 : 汎化 |
public class SchedulerDAOImpl implements SchedulerDAO {
...
}
Java の実装は UML の実現 ( realize ) に対応します。実装クラスからインタフェースに破線を引き、先端に△をつけて表します ( 図 9 )。インタフェースをアイコン表示した場合は、実装クラスとインタフェースを実線で結びます ( 図 10 )。
![]() |
![]() |
図 9 : インタフェースの実装 | 図 10 : インタフェースの実装(アイコン表示) |
先程 Content クラスでは、 Java の「フィールド」を UML の「属性」で表しましたが、もう一つ「関連 ( association ) 」で表す方法があります。
public abstract class Schedule {
private Content content;
...
}
Schedule クラスは Content クラスへの参照を持っていますが、これを図 11 のように表します。フィールドを持つクラスから型に対応するクラスへ実線矢印を引き、型に対応するクラスの側にフィールド名 ( UML ではロール名 ) を記入します。
![]() |
図 11 : フィールドを関連で表す |
Java のフィールドは、属性と関連のどちらで表すことも可能ですが、主要なクラス間の関係は「属性」より「関連」で記述した方が、システムの概要を把握しやすくなります。およそ表 2 のような基準で使い分けると良いでしょう。値オブジェクトとは、 String や Date など、いくつでもコピー可能で、 equals( ) によって同値性を調べることができるオブジェクト、参照オブジェクトはそれ以外のオブジェクトです。
フィールドの型 | 表現方法 (属性もしくは関連) |
---|---|
基本型 ( boolean、 int、 double など) | 属性 |
値オブジェクト ( String、 Date、 Time、 BigInteger、 BigDecimal など) | 属性 |
参照オブジェクト | 関連 |
配列、コレクション ( List、Map など) | 要素による ( 後述 ) |
フィールドの型にコレクションクラス ( List、Map など ) や配列が使われている場合について説明しておきましょう。現在の Java の仕様 ( 最新は 1.4 ) では、コレクションクラスの要素は Object 型になっていますが、 UML で表記する場合は、実際に格納されるオブジェクトの型を意識する必要があります。
ScheduleList クラス ( リスト 3 ) は java.util.List 型のフィールド schedules を持っています。フィールド名からもコレクションの要素が何かおよそ検討はつきますが、add( ) 、remove( ) の引数を見ると、この List に格納されるオブジェクトは Schedule クラス ( またはその子クラス ) のインスタンスであることがわかります ( *5 )。このような場合、 UML では ScheduleList クラスから Schedule クラスへ関連を引き、 Schedule を複数個持てることを示すために、 Schedule の側に多重度 * をつけます ( 図 12 ) 。「多重度」の書き方は表 3 のとおりです ( * と 0..* は同じ意味を表します)。
package scheduler.domain;
import java.util.*;
/** 予定リスト. */
public class ScheduleList {
// List<Schedule>
private List schedules = new ArrayList();
public List getAll() {
return schedules;
}
public void add(Schedule schedule) {
schedules.add(schedule);
}
public void remove(Schedule schedule) {
schedules.remove(schedule);
}
...
}
![]() |
図 12 : コレクションの表し方 |
多重度の表現例 | 意味 |
---|---|
0..1 | 0 または 1 |
1 | 1 |
* | 0 以上 (上限なし) |
0..* | 0 以上 (上限なし) |
1..* | 1 以上 (上限なし) |
m..n | m 以上 n 以下 |
m, n | m または n |
次に Map ですが、 Scheduler クラス ( リスト 4 ) は java.util.SortedMap 型のフィールド fixedSchedules を持っています。この fixedSchedules に関しては、getFixedSchedules( ) をよく見ると、Date 型の変数 date をキーに ScheduleList オブジェクトを取得していることがわかります ( *6 )。UML ではこれを図 13 のように「限定子つき関連」で表します。限定子が date : Date、関連先のクラスが ScheduleList になります。多重度は限定子 date の「ある値」に対して、 ScheduleList オブジェクトが何個あるかで決定されるので、注意してください。
![]() |
図 13 : 限定子つき関連 |
最後は配列です。同じクラス Scheduler のもう一つのフィールド weeklySchedules を見てみましょう。ScheduleList の配列と宣言され、サイズ DAYS_OF_WEEK = 7 で初期化されています。このように配列の場合は、格納されるオブジェクトの型が明記されていますから、単純にその型へ関連を引き、適当な多重度 ( ここでは 7 ) をつければ完成です( 図 14 )。
![]() |
図 14 : 固定長配列の表し方 |
なお、ここまでコレクションや配列の要素が参照オブジェクトの場合を見てきましたが、要素が基本型や値オブジェクトの場合は、普通に属性で表せば良いでしょう。
package scheduler.domain;
import java.sql.Date;
import java.util.*;
/** スケジューラ. */
public class Scheduler {
static final int DAYS_OF_WEEK = 7;
// SortedMap<Date, ScheduleList>
private SortedMap fixedSchedules = new TreeMap();
private ScheduleList[] weeklySchedules
= new ScheduleList[DAYS_OF_WEEK];
public ScheduleList getFixedSchedules(Date date) {
ScheduleList list = (ScheduleList)fixedSchedules.get(date);
if (list == null) {
list = new ScheduleList();
fixedSchedules.put(date.clone(), list);
}
return list;
}
public void addFixedSchedule(FixedSchedule schedule) {
Date date = schedule.getDate();
getFixedSchedules(date).add(schedule);
}
public void removeFixedSchedule(FixedSchedule schedule) {
Date date = schedule.getDate();
getFixedSchedules(date).remove(schedule);
}
...
}
クラス図のモデル要素も主要なものはこれで最後です。参照をフィールドに持たないため関連はないのですが、メソッドの引数や戻り値、ローカル変数として使われているクラスとの関係を明示したい場合、「依存関係 ( dependency ) 」を引くことができます。たとえば、 Scheduler クラス ( リスト4 ) であれば、 FixedSchedule クラスが引数で使われているので、依存関係を引くことができます。 UML で依存関係は破線矢印で表します ( 図 15 )。
![]() |
図 15 : 依存関係 |
ここまで説明した表記法を用いて domain パッケージのクラスを UML のクラス図で表したのが図 16 です。なお、コンストラクタ、アクセッサ ( get / set )、コレクションの add / remove などは図から省略しています。
![]() |
図 16 : domain パッケージ |
![]() |
「よし、これで domain パッケージの構造はだいたいわかったぞ。この調子でほかのパッケージのクラス図も描いてみよう... ( 紙面の都合で省略します ) 」 |
---|
今回の最後に、プログラム全体の概要を把握するのに便利なパッケージ図を紹介したいと思います。UML でパッケージ ( package ) は図 17 のようなタブつきの長方形で表します。各パッケージに属するクラスの間に、関連 ( 図 18 ) や汎化関係、依存関係がある場合、パッケージ間にも依存関係を引くことができます ( 図 19 )。
![]() |
図 17 : パッケージ |
![]() |
![]() |
図 18 : 異なるパッケージに属するクラス間の関連 | 図 19 : パッケージ間の依存関係 |
スケジューラの各パッケージ間の関係は調査の結果、図 20 のようになっていることがわかりました。
![]() |
図 20 : スケジューラのパッケージ図 |
今日の作業を終えて、 Chen 君が一息ついていると、 Jun 先輩が様子を見にやってきました。
![]() | 「どうだい、進み具合は ? 」 |
---|---|
![]() |
「はい、クラス図とパッケージ図はかけました。システムの静的な構造はだいたい理解できたと思います。明日はシステムの振る舞いを調べてみようと思います。」 |
![]() | 「どれどれ。うん、なるほど。私はソースコードを見ている時間はちょっとないが、これならひと目見て概要がつかめそうだ。じゃあ、明日も頼むよ。」 |
今回、ソースコードと対比しながら、システムの論理的な構造を表す UML のダイアグラム「クラス図」と「パッケージ図」を説明してきましたが、いかがだったでしょうか? 図で表すことで、システムの概要が理解しやすくなったと思いませんか?
次回は、システムの振る舞いを表す UML のダイアグラムを紹介したいと思います。どうぞご期待ください。
連載記事一覧
© 2002 - 2003 OGIS-RI Co., Ltd. |
|