オブジェクト指向とUML

技術の仕組み | 開発技法

← 技術の仕組み

情報システム開発の設問で図が出てきたとき、読むべきものはほぼ2つです。線の先端に付いた記号(どういう関係か)と線の両端に書かれた数字(多重度=いくつ結び付くか)。この2つを取り違えると、空欄の答えがまるごと変わります。逆に言えば、記号と多重度さえ正確に読めれば、クラス図の設問は大半が機械的に片づきます。

先に押さえる

オブジェクト指向は「データ(属性)とそれを扱う手続き(操作)を1つのまとまりにする」考え方で、そのまとまりの設計図がクラス、実際に動く実体がオブジェクト(インスタンス)です。UMLはその設計を描くための共通の記法。クラス図=静的な構造(誰と誰が、いくつ、どう関係するか)シーケンス図=動的なふるまい(誰が誰を、どの順で呼ぶか)。何を見せたいかで図が分かれているだけです。

クラス図の読み方

クラスは3段の箱で描きます。上からクラス名/属性/操作。属性はそのクラスが持つデータ、操作はそのクラスができることです。箱と箱をつなぐ線が関連で、線の端に多重度を書きます。

クラスの箱と関連(1人の会員が0個以上の注文を持つ)

会員クラス名
会員番号
氏名
住所
住所を変更する()
注文履歴を返す()
1 0..*

関連(持つ)

注文
注文番号
注文日
合計金額
合計を計算する()
取り消す()

この図は「会員1人に対して注文は0個以上」「注文1件に対して会員はちょうど1人」と読みます。多重度は相手側の端に書かれた数を読むのが原則で、自分の側の数字ではありません。ここを逆に読むと全問ずれます。

記号の意味を図で区別する

試験で問われるのは、ほぼこの3つの記号です。線のどちら側に付いているか(全体側か部分側か、親側か子側か)まで含めて覚えます。

汎化・継承(白抜き三角)

〜は〜の一種である」の関係。三角は親(一般的なほう)側に付く。子は親の属性・操作を受け継ぐ。

例:正社員/派遣社員 は 従業員 の一種

集約(白ひし形)

全体と部分」だが、部分は全体が消えても単独で存在できる。ひし形は全体側に付く。

例:チームと社員(チーム解散でも社員は残る)

コンポジション(黒ひし形)

集約より強い全体と部分。全体が消えると部分も消える(生存期間を共にする)。ひし形は全体側

例:注文と注文明細(注文が消えれば明細も消える)

つまずきやすい

白と黒のひし形を「なんとなく強さの違い」で済ませると選択肢で迷います。判断は「全体を消したとき、部分は生き残るか」の一点。生き残る=白ひし形(集約)、道連れで消える=黒ひし形(コンポジション)。そしてひし形は必ず全体側三角は必ず親側。記号の付く向きが逆の選択肢は、それだけで切れます。

汎化(△は親側)とコンポジション(◆は全体側)

従業員抽象クラス
氏名
所属
給与を計算する()《抽象》

汎化(〜の一種)

正社員
月給
給与を計算する()
時間給社員
時給/勤務時間
給与を計算する()
注文全体
注文番号/注文日
1..*

コンポジション(消えたら道連れ)

注文明細部分
商品/数量/単価

多重度の読み方

表記意味読みどころ
1ちょうど1つ必ず1つ結び付く。0も2つ以上も許さない
0..10か1つ「あってもなくてもよい」=省略できる関係
0..*0以上(いくつでも)1つも無くてよい。空の状態が正常として存在する
1..*1以上(最低1つ必要)0は許されない。必ず1つ以上ぶら下がる
*0..*と同じ下限が書かれていなければ0が許される
見抜きどころ

解答が割れるのは0..*1..* の差です。問題文には必ず根拠が書いてあります。「明細を1件以上含む」「少なくとも1つ登録する」なら 1..*。「まだ注文していない会員も登録できる」「0件の場合もある」なら 0..*。本文の「〜がない場合もある」「必ず〜を持つ」という一文が、そのまま下限0か1かを決めています。図を眺めて決めるのではなく、本文の一文を根拠に引くのが正解への最短路です。

オブジェクト指向の4本柱

カプセル化

  • データと操作を1つに包む
  • 属性は直接いじらせず操作経由
  • 効果:内部を変えても外に波及しない

継承

  • 親の属性・操作を受け継ぐ
  • 共通部分を親に1か所だけ置く
  • 効果:重複が消える/修正が1か所

ポリモーフィズム

  • 同じ呼び方で相手ごとに違う動き
  • 呼ぶ側は相手の種類を知らなくてよい
  • 効果:種類を増やしても呼ぶ側は無修正

抽象化

  • 本質だけ残して細部を隠す
  • 抽象クラス:共通処理は書き、個別処理は「あとで各自が書く」と宣言だけする
  • インタフェース呼び口(操作の名前と引数)だけを決め、中身は一切持たない

「共通の呼び口」の役割

  • 抽象クラス/インタフェースは約束事
  • 呼ぶ側は約束だけ見て呼ぶ
  • 実装が10種類に増えても呼ぶ側のコードは変わらない

4本柱はバラバラの用語ではなく、「変更に強くする」ための一本の筋です。カプセル化で影響範囲を閉じ、抽象化で呼び口を固定し、継承で共通部分を1か所に集め、ポリモーフィズムで種類の追加を呼ぶ側から隠す。設問でこれらの効果を問われたら、答えは大体「変更・追加のときに直す箇所が減る」方向です。

シーケンス図の読み方

クラス図が「誰と誰が関係するか」なのに対し、シーケンス図は「誰が誰を、どの順で呼ぶか」を描きます。時間軸は縦(上から下へ)メッセージは横向きの矢印。上に並んだ箱がオブジェクトで、そこから下に伸びる縦の点線が生存線です。

↓ 時間は上から下へ/横矢印=メッセージ(操作の呼び出し)

画面
注文管理
在庫管理
1画面 → 注文管理:注文を確定する(注文内容)
2注文管理 → 在庫管理:在庫を引き当てる(商品, 数量)
3在庫管理 → 注文管理:戻り(引当結果)=破線の矢印
4注文管理 → 注文管理:合計を計算する()(自分自身への呼び出し)
5注文管理 → 画面:戻り(注文番号)
注記

実線の矢印が呼び出し、破線の矢印が戻り。生存線の上に細長く重なる長方形は実行仕様(そのオブジェクトが処理中である期間)を表します。空欄補充で問われるのは「この位置の矢印は誰から誰へ、どの操作か」で、根拠はクラス図でその操作を持っているのは誰かにあります。2つの図を行き来して答えるのが定石です。

デザインパターンの例:Observer

デザインパターンは、よく出る設計の型に名前を付けたものです。代表格のObserver(オブザーバ)は「状態が変わったら、登録済みの相手にまとめて通知する」型。通知する側(被験者)と通知される側(観察者)をインタフェースで切り離すのが要点です。

Observer:通知する側は「観察者」という呼び口しか知らない

在庫データ通知する側
在庫数
登録する(観察者)
解除する(観察者)
変更を通知する()
1 0..*

通知先として保持

観察者<<interface>>
更新された()

実現(破線+白抜き三角)
この呼び口を備える

一覧画面
更新された()
グラフ表示
更新された()
発注アラート
更新された()

インタフェースと実装クラスを結ぶ実現は、破線+白抜き三角で描きます。実線+白抜き三角の汎化とは線の種類で区別する——三角が同じなので、線が実線か破線かを見落とすと汎化と取り違えます(クラスを継いだのか、呼び口だけを備えたのか)。
在庫データが持っているのは「観察者」という呼び口のリストだけで、相手が一覧画面なのかグラフなのかは知りません。だから通知される側を後から増やしても、在庫データ側は1行も直さなくてよい——これがこのパターンの利点であり、設問で問われる効果そのものです。多重度が 0..* なのは、誰も登録していない状態も正常だから。ここも本文の根拠と対応します。

設問での急所

見抜きどころ

クラス構成を答える問題の判断軸は「共通処理は抽象側、個別処理は具象側」です。複数のクラスに同じ処理が重複して書かれていたら親(抽象クラス)へ引き上げる、逆に種類ごとに中身が違う処理は抽象側では名前だけ宣言し、具象側で実装する。この配置にしておくと、種類を1つ追加しても新しい子クラスを足すだけで済み、既存のコードを触らずにすみます。「拡張しやすい設計はどれか」「変更の影響を小さくするにはどう直すか」と問われたら、ほぼこの形が答えです。

つまずきやすい

もう一つの失点源が多重度の下限0..*1..* は図の見た目がほぼ同じなのに、意味は「無くてよい/必ず要る」で正反対です。答案を書く前に、必ず問題文へ戻って「0件のケースが本文に書かれているか」を確認してください。書かれていれば 0..*、「必ず1つ以上」「少なくとも1件」とあれば 1..*。ここだけで配点が動きます。

つながる仕組み

関連→ 排他制御と競合状態(設計した処理が同時実行で壊れる話)

この仕組みを使う設問