はじめに
オブジェクト指向を学ぶ上で、クラス図は設計を視覚的に理解するための重要なツールです。
一方で、継承や集約といった用語は知っていても、図の矢印の向きや線の記号が何を表しているかで迷うことは少なくありません。
この記事では、設計書やドキュメントのクラス図を「記号と多重度まで含めて読める」状態になることを目標に、基本的な読み方を整理します。
クラス図とは
クラス図は、システムの構造を表現するUML図の一つです。
クラス(設計図)とその関係性を視覚的に表現できます。
クラス図の基本構造
シンプルなクラス図
ToDoアプリの 1件のやること を表す Task を例にします。
- 何のための処理か: 画面に出す文言や一覧の並びを決めるために、タイトルと完了状態をまとめて持ち、完了にしたり表示用の文字列を作ったりする。
-
Taskがしたいこと: 「誰が見ても同じルールで、タスクを完了済みにできる」「表示用のテキストをここから取れる」ように、データ(id / title / 完了か)と操作(complete/getDisplayText)を1か所にまとめる。
クラス図の各要素
Task = クラスの名前
string id = フィールド(プロパティ)と型
+complete() ... = メソッド(操作)。+は可視性、括弧内は引数、戻り値の型などが続くことが多いです(ツールや図のテンプレートによって表記が揺れます)
可視性(アクセス修飾子)
クラスやフィールドの公開範囲を記号で表現します。
| 記号 | 可視性 | 意味 | 使い分け |
|---|---|---|---|
| + | public | どこからでもアクセス可能 | 外部に公開するAPI |
| - | private | そのクラス内からのみアクセス可能 | 内部実装の詳細 |
| # | protected | そのクラスと継承先からアクセス可能 | 継承先で使わせたい処理 |
| ~ | package | 同じパッケージ内からアクセス可能 | モジュール内での共有 |
ToDoアプリでの例
続けて同じ Task を使い、**「外部に見せていい部分」と「中だけで完結させたい部分」**を記号(+ / -)で分けた図です。
-
何のための処理か: UIや他のクラスから
_isCompletedを直接いじられると不整合が起きやすいので、完了フラグは隠し、検証や更新はメソッド経由にする。 -
Taskがしたいこと:idとtitleは識別・表示に必要なので公開(+)し、完了の内部表現とチェック処理は非公開(-)にして、安全にcompleteだけを呼べるようにする。
class Task {
// public: どこからでもアクセス可能
public id: string;
public title: string;
// private: クラス内部でのみ使用
private _isCompleted: boolean = false;
constructor(id: string, title: string) {
this.id = id;
this.title = title;
}
// public メソッド: 外部から呼び出せる
public complete(): void {
if (this.validate()) {
this._isCompleted = true;
}
}
// public ゲッター: 読み取り専用で公開
public get isCompleted(): boolean {
return this._isCompleted;
}
// private メソッド: 内部処理用
private validate(): boolean {
return this.title.length > 0;
}
}
const task = new Task("1", "牛乳を買う");
console.log(task.title); // OK: publicなのでアクセス可能
console.log(task.isCompleted); // OK: ゲッター経由でアクセス
// console.log(task._isCompleted); // エラー: privateなのでアクセス不可
// task.validate(); // エラー: privateなのでアクセス不可
クラス間の関係を表す線
クラス図では、クラス同士の関係を線で表現します。
| 線の種類 | この記事の図での例 | 意味 |
|---|---|---|
| 関連 | --> |
クラス間のつながり |
| 継承 | <|-- |
親子関係(is-a) |
| 実装 | <|.. |
インターフェースの実装 |
| 集約 | o-- |
全体と部分(弱い関係) |
| コンポジション | *-- |
全体と部分(強い関係) |
| 依存 | ..> |
一時的な利用関係 |
**依存(Dependency)**は、フィールドとして常に持っている、というより メソッドの引数・戻り値・局所変数などを通じて一時的に使う 関係を表すことが多いです。
例:ToDoのユースケースでいうと、TaskService が TaskFactory を使って新しい Task を生成する、など。
図にすると次のイメージです(フィールドでずっと持っているのではなく、作るときだけ使うので破線の依存になりやすい、という読み取りの練習用です)。
- 何のための処理か: 画面から「下書きタスクを1件作る」操作が来たとき、IDの採番や初期状態のルールをサービス側に書きたくないので、生成だけを工場に任せる。
-
TaskServiceがしたいこと: アプリの流れ(ユースケース)を組み立てる。必要になった瞬間にTaskFactoryを呼ぶ。 -
TaskFactoryがしたいこと: 「タイトルが与えられたら、ルールどおりのTaskを返す」ことだけに集中する。 -
Taskがしたいこと: 生成されたあとは、これまで通り「1件のやること」としてデータを保持する。
ToDoアプリでの関係図
ここからは クラス同士の線の種類 を、ToDoアプリのミニ構成で並べた図です(パターン名より先に、「線が何を意味するか」を追いかけます)。
- 何のための処理か: 「誰が作ったか」「どのまとまりに属するか」「チェック項目はタスクにぶら下がる」といった、データ同士のつながりを設計として固定したい。
-
Userがしたいこと: アカウントとして存在し、自分が作成したタスクとの関係を表す(図では「作成」という関連)。 -
Taskがしたいこと: 1件のやることとして存在し、作成者IDを持つ(誰のタスクか分かるようにする)。 -
Projectがしたいこと: 複数タスクを束ねる箱。プロジェクトから外してもタスクが生き残る想定なので、集約(白ひし形)の例にする。 -
SubTaskがしたいこと: 1タスクの中の細かいチェック項目。親タスクがいなければ意味が薄いので、コンポジション(黒ひし形)の例にする。
多重度(カーディナリティ)
関連線の端に書かれる数字は「何個と何個が対応するか」を示します。
| 記号 | 意味 | 例 |
|---|---|---|
| 1 | 必ず1つ | タスクの作成者は必ず1人 |
| 0..1 | 0または1つ | 担当者は未設定でもOK |
| * | 0以上(複数可) | ユーザーは複数のタスクを持てる |
| 1..* | 1以上 | プロジェクトには最低1つのタスクが必要 |
| n..m | n以上m以下 | チームメンバーは2〜10人 |
多重度は、**「その関係を満たすために、最低いくつ必要か/多くてよいか」**を数字で縛るための記号です。次の図は、ユーザーとタスクの間に 2種類の役割(作成者と担当者) を置いた例です。
- 何のための処理か: タスクは必ず誰かが作るが、担当者は後から決まる/空でもよい、という業務ルールを図に落とす。
-
User(作成側)がしたいこと: 自分が作ったタスクが複数あってよい、という関係を表す(creates)。 -
User(担当側)がしたいこと: タスクに担当がいなくてもよいし、いても1人まで、という関係を表す(assigned toの0..1)。 -
Taskがしたいこと:creatorIdで作成者を必ず指し、assigneeIdで担当を任意に持つ。
この図の読み方:
- 1人のユーザーは複数(*)のタスクを作成できる
- タスクには必ず1人の作成者がいる
- タスクの担当者は0人または1人(未設定可)
ステレオタイプ
<<interface>>や<<abstract>>のような表記はステレオタイプと呼ばれ、
クラスの特殊な性質を示します。
ここでは、**「この箱は普通のクラスではない」**ことをステレオタイプで宣言している例です。名前(Repository / Notifier)に馴染みがなくても大丈夫で、図が言っていることは次の2点です。
-
TaskRepositoryがしたいこと: 「タスクをどこに保存するか」は実装ごとに変わり得るので、保存・取得などの操作名だけ先に決めておく(インターフェース)。実際のDBやAPI呼び出しは別クラスが担当する、という分割の入口です。 -
BaseNotifierがしたいこと: メールやSlackなど手段は違っても、通知の流れ(文面整形など)の共通部分は親に寄せ、送る処理だけ子に実装させる(抽象クラス)。BaseNotifier自体はそのままでは作れない、という意味も込めます。
| ステレオタイプ | 意味 |
|---|---|
<<interface>> |
インターフェース(実装を持たない契約) |
<<abstract>> |
抽象クラス(インスタンス化できない) |
<<enumeration>> |
列挙型 |
<<service>> |
サービスクラス |
まとめ
| 要素 | 意味 |
|---|---|
| クラス名 | 設計図の名前 |
| フィールド | クラスが持つデータ |
| メソッド | クラスが持つ振る舞い |
| 可視性(+/-/#/~) | アクセス範囲 |
| 関係線 | クラス同士のつながり |
| 多重度 | 対応する個数 |
| ステレオタイプ | クラスの特殊な性質 |
クラス図が読めるようになると、設計書やライブラリのドキュメントが理解しやすくなります。
矢印と記号が読めると、継承・インターフェース・集約といった「関係の種類」をコードの責務分担に落とし込みやすくなります。