変更のたびに修正先が増える?|クラス・責務・多相性・Observer
在庫が変わるたびに、画面更新や補充判定のコードを在庫クラスへ追加し続けてよいのでしょうか。機能を増やすほど、修正の影響が広がる場合があります。何を知る担当か、誰へ知らせる担当かを分けて考えます。
事例:在庫を引き当て、表示と補充判定へ知らせる
在庫は40個です。注文で3個を引き当てると37個になり、画面表示と補充判定へ新しい値を知らせます。補充が必要な条件は20個未満とします。注文数量は正の整数で、在庫不足なら更新しません。
在庫の管理、画面表示、補充判定を別の担当へ分けます。通知先が増えても、在庫管理側が通知先ごとの詳しい処理を持たない構成を考えます。
- 1. 3個を引当
- 2. 新しい在庫37
- 3. 新しい在庫37
矢印は操作・通知の方向です。通知先は共通の更新操作を持ち、在庫側から呼び出されます。
クラスとオブジェクトは、何が違う?
クラスは、状態と操作のまとまりを定義します。オブジェクトはその具体的な実体です。同じ在庫クラスから商品Aと商品Bの実体を作っても、通常はそれぞれの在庫数を持ちます。
- 状態
各実体が持つ値です。この例では在庫数や登録された通知先です。
- 操作
状態に対して許される処理です。この例では引当や通知先の登録です。
- 責務
担当する判断・処理の範囲です。在庫不足の判断は在庫管理側が持ちます。
データを隠す目的は、業務上の条件を守って変更できるようにすることです。外部が在庫数を直接自由に書き換えると、負数の禁止や通知を迂回できてしまいます。アクセス範囲と操作の契約を対応させます。
関連・集約・合成・継承を読み分ける
クラス図の関連は、実体の間のつながりを表します。多重度は何個とつながるかを示します。例えば在庫管理に0..*個の通知先を登録できるなら、通知先がゼロでも存在できる関係です。
関係 | 読むポイント | この例での注意 |
|---|---|---|
関連 | 他の実体とつながる | 在庫管理が通知先を参照する |
集約 | 全体と部分の関係 | 共有等の具体的な意味はモデルで確認する |
合成 | 部分の所有・寿命を全体へ結び付ける強い関係 | 外部でも使う通知先を当然に合成としない |
汎化 | 一般的な型を特化する継承関係 | 種類として置き換えられるかを見る |
実現 | インタフェース等の契約を実装する | 共通の更新操作を各通知先が実装する |
「参照しているから継承する」とは限りません。在庫管理は表示の一種ではないため、表示クラスを継承する関係にはしません。クラス図の矢印・ひし形・多重度は、それぞれの記号の意味を確認します。
多相性で、呼び出す側は何が楽になる?
通知先にupdate(stock)という共通操作を設けます。在庫側はこの操作を呼び、画面表示は表示を更新し、補充判定は閾値と比べます。同じ呼出しに対して実体ごとの実装が働く多相性を利用します。
型ごとにifを増やして処理を切り替える方式と違い、新しい通知先が共通の契約を満たせば登録できます。ただし、引数の意味、例外、更新のタイミングなどの契約まで一致させる必要があります。
継承した型は、呼出し側が期待する条件を壊さず使えることが重要です。名前が似ている、コードを再利用できるという理由だけで継承を選ぶと、責務や操作の条件が合わなくなる場合があります。
Observerは、何を結び付ける仕組み?
Observerは、状態の変化を知りたい側を登録し、変化した側が通知する設計です。在庫管理を通知元、画面表示と補充判定を通知先として使います。通知元が個々の具体的なクラスを詳しく知らずに済みます。
この事例では同期的に通知します。登録・解除、通知順、例外の方針は別途決める条件です。
Observerという設計名だけでは、非同期実行、別スレッド、永続配送、順番の保証まで決まりません。Javaの旧Observer・Observable APIもこの設計全体と同一ではなく、現在は非推奨です。ここでは独自の共通操作を使います。
小さなコードで、担当の違いを確かめる
class Observer:
def update(self, stock):
raise NotImplementedError
class Display(Observer):
def update(self, stock):
self.shown = stock
class Replenishment(Observer):
def update(self, stock):
self.needed = stock < 20
class Inventory:
def __init__(self, stock):
self.stock = stock
self.observers = []
def subscribe(self, observer):
self.observers.append(observer)
def reserve(self, quantity):
if quantity <= 0 or quantity > self.stock:
raise ValueError("invalid quantity")
self.stock -= quantity
for observer in tuple(self.observers):
observer.update(self.stock)この最小例は整数の数量を受け、登録順に同期呼出しします。通知先一覧のコピーを巡回するため、処理中の一覧変更で巡回対象が変わるのを避けます。ただし、このコピーだけで複数スレッドからの変更を安全にはできません。
通知先で例外が出ると、このコードでは後続の通知が止まり、変更済みの在庫は戻りません。業務で必要なら、継続・再通知・エラー記録の方針を追加します。通知の失敗と引当の失敗を区別することが設計上の判断です。
通知先を外し忘れると、何が起こる?
使わなくなった画面が登録されたままだと、不要な通知が続いたり、参照が残って解放されなかったりします。画面の寿命に合わせて解除する操作と、重複登録を許すかを決めます。
通知先の処理が遅い場合、同期通知では引当の応答も遅くなります。非同期へ変えるなら、順序、最新値と途中の変化、失敗時の再試行、重複の扱いを追加で設計します。
機能を分ける目的はクラス数を増やすこと自体ではありません。業務の条件を守る担当と、変更されやすい通知後の担当を分け、修正の影響範囲を理解できる状態にします。
演習1:状態の変更
条件:在庫40、引当数量3、補充条件は20未満です。
問い:変更後の在庫と補充判定を答えてください。
解答例:在庫37で、補充は不要です。
根拠:40-3=37で、37<20は偽です。
誤答の理由:通知先が二つあるから二回在庫を減らすという判断は、引当と通知を混同しています。
演習2:共通の操作
条件:表示と補充判定はいずれもupdate(stock)を実装します。
問い:在庫側が通知先を一件追加する際、何を前提にできますか。
解答例:新しい通知先も共通操作とその契約を満たすなら、同じ呼出しで通知できます。
根拠:具体的な通知先ごとの処理は、その実体の実装が担当します。
誤答の理由:通知先を追加すれば在庫側のif分岐も必ず追加する説明では、多相性を使う目的を捉えていません。
演習3:関係の選択
条件:在庫管理が画面表示を参照して通知します。
問い:在庫管理は画面表示を継承すべきですか。
解答例:この関係だけでは継承しません。参照・通知の関連として扱います。
根拠:在庫管理は画面表示の一種として置き換えられるものではありません。
誤答の理由:利用する相手をすべて親クラスにすると、責務と型の関係が合わなくなります。
演習4:通知とスレッド
条件:掲載コードは登録した相手のupdateをその場で呼びます。
問い:通知先は別スレッドで動きますか。
解答例:このコードでは別スレッドへ切り替えず、同期的に呼び出します。
根拠:通常の関数呼出しだけで、スレッドやキューを作っていません。
誤答の理由:Observerという名称だけで非同期と判断すると、実行時間や例外の伝播を取り違えます。
演習5:不正な引当
条件:在庫40に対して数量41を指定します。
問い:掲載コードで在庫と通知はどうなりますか。
解答例:例外となり、在庫は40のまま、通知しません。
根拠:数量検証が在庫の変更と通知より先にあります。
誤答の理由:在庫を一度負数へ変えてから戻すという追跡は、掲載コードの順序と異なります。
演習6:通知失敗の扱い
条件:引当後、最初の通知先のupdateが例外を発生させました。
問い:掲載コードの結果と、追加で決める方針を答えてください。
解答例:在庫は変更済みで、後続の通知は止まります。通知の継続・再試行・記録の方針を決めます。
根拠:在庫更新後に通知しており、例外の捕捉や取消しを実装していません。
誤答の理由:全処理が自動で取り消されるという説明は、通知と状態変更の境界を無視しています。
出典と仕様を確認する
Oracle Java SE 25:旧Observable APIの制限
関連テーマを続けて学ぶ
次におすすめの学習
編集・検証について
編集・検証:IT資格ラボ編集部
IPAが公開する試験要綱・シラバス・過去問題と、各技術の公式資料を優先して内容を確認しています。制度変更や誤りを確認した場合は、記事を見直して更新します。
編集方針・情報源・訂正方針を見る