Instiq
第3章 · ソフトウェア方式設計·v1.0.0·更新 2026/7/11·読了目安 約17分

変更要約: 初版

3.2オブジェクト指向設計とUML

この節の要点

オブジェクト指向設計の3要素(カプセル化/継承/多態)と、変更に強い設計原則であるSOLID原則(単一責任・開放閉鎖・リスコフの置換・インタフェース分離・依存性逆転)、そしてUML(クラス図・シーケンス図・状態遷移図)をどの設計判断の可視化に使い分けるかを、実際の要求変更への対応という判断とともに学びます。

オブジェクト指向設計の価値は「クラスを作ること」自体ではなく、将来の要求変更に対してどれだけ既存コードへの手直しを最小限にできるかにあります。システムアーキテクトにとって重要なのは、カプセル化・継承・多態という基本要素をどう組み合わせれば変更に強くなるかを判断でき、SOLID原則のどれに違反しているために設計が硬直化しているのかを見抜けることです。

3.2.1オブジェクト指向の3要素

  • カプセル化=データとそれを操作する処理を1つのオブジェクトにまとめ、内部状態を外部から直接操作させず、公開されたインタフェース(メソッド)経由でのみアクセスさせる原則。内部実装を変更しても呼び出し側に影響を与えないことが目的。
  • 継承=既存クラス(親)の性質を新しいクラス(子)が引き継ぎ、差分のみを追加・上書きする仕組み。共通処理の再利用に有効だが、親クラスの変更が全ての子クラスに波及するため、無秩序な多段継承は結合度を高める副作用がある。
  • 多態(ポリモーフィズム)=同一のインタフェース(メソッド呼び出し)に対して、実際のオブジェクトの型ごとに異なる振る舞いをさせる仕組み。呼び出し側は具体的な型を意識せずに済むため、新しい型(振る舞い)を追加しても呼び出し側のコードを変更せずに済むことが最大の利点。

3.2.2SOLID原則

原則要旨
単一責任(SRP)1つのクラスが変更される理由は1つだけであるべき。複数の関心事を持つクラスは、一方の変更が他方に無関係な影響を及ぼす。
開放閉鎖(OCP)**拡張に対して開いており、既存コードの修正に対して閉じている**べき。新しい振る舞いの追加は新クラスの追加で行い、既存クラスの中身は書き換えない。
リスコフの置換(LSP)親クラス型の変数はどの子クラスのインスタンスに差し替えても、プログラムの正しさが保たれるべき。子クラスが親の契約(事前条件/事後条件)を破ってはならない。
インタフェース分離(ISP)クライアントは自分が使わないメソッドへの依存を強制されるべきではない。**巨大な汎用インタフェースより、用途別の小さなインタフェース**を複数用意する。
依存性逆転(DIP)上位モジュールは下位モジュールの具象実装に直接依存せず、両者が**抽象(インタフェース)に依存**すべき。実装の詳細を後から差し替え可能にする。
試験ポイント

「開放閉鎖原則=拡張に開き修正に閉じる」「リスコフの置換原則=子クラスは親の契約を破ってはならない」「依存性逆転原則=上位は下位の具象ではなく抽象に依存する」が最頻出です。多態は新しい型を追加しても呼び出し側の既存コードを変更せずに済む点が開放閉鎖原則と直結することを押さえましょう。

あるシステムアーキテクトが、決済処理を担うクラスPaymentProcessorの設計をレビューしているとします。現状の実装はif (type "credit") {...} else if (type "bank_transfer") {...} else if (type "e_wallet") {...}という条件分岐で決済方式ごとの処理を1つのメソッド内に列挙しており、新しい決済方式(例:QRコード決済)を追加するたびにこのメソッドを直接書き換える必要がありました。これは開放閉鎖原則(OCP)に違反する典型例です——拡張(新しい決済方式の追加)のたびに既存の分岐ロジックを修正しているため、修正時に既存の決済方式(クレジット/銀行振込)の処理を誤って壊すリスクが常に伴います。改善策として、PaymentMethodという共通インタフェース(pay()メソッドを持つ)を定義し、CreditCardPaymentBankTransferPaymentEWalletPaymentという多態を活用した具象クラスに分割します。PaymentProcessorPaymentMethod型の変数を通じてpay()を呼び出すだけにすれば(依存性逆転原則(DIP):上位モジュールであるPaymentProcessorが下位の具象実装ではなく抽象PaymentMethodに依存)、新しい決済方式(QRコード決済)を追加する際はQrCodePaymentという新クラスを追加するだけで済み、PaymentProcessorの既存コードは一切変更不要になります。これは開放閉鎖原則を満たした状態です。ただし、この設計変更を行う際はリスコフの置換原則(LSP)にも注意が必要です。仮にEWalletPaymentが「決済額が上限を超えると必ず例外を投げる」というPaymentMethodの契約にない挙動を追加してしまうと、PaymentProcessor側がEWalletPaymentをほかの決済方式と同様に扱えなくなり、多態の前提が崩れます。「要求変更のたびに既存コードの分岐を書き換えている」という症状を見たら、多態による分割とDIPで開放閉鎖原則を満たす設計に改めるのがシステムアーキテクトの判断です。

3.2.3UMLの使い分け

  • クラス図=クラス間の静的な構造(属性・操作・継承関係・関連・依存関係)を表現する。SOLID原則違反(巨大インタフェース、具象への直接依存等)は多くの場合クラス図の関連線を見れば発見できる。
  • シーケンス図=特定のユースケース(シナリオ)における、オブジェクト間の時系列のメッセージのやり取りを表現する。「どのオブジェクトがどの順でどのメソッドを呼ぶか」という動的な協調の流れを検証したいときに使う。
  • 状態遷移図は、1つのオブジェクトが取りうる状態と、状態間を移す事象(イベント)を表現する。決済処理の「未処理→処理中→完了/失敗」のように、状態遷移のルールそのものが複雑・重要な対象の設計・検証に向く。
注意

ひっかけ: 「継承を多用してコード量を減らすことは常に良い設計である」は誤りです——親クラスの変更が全子クラスへ波及する結合を生むため、共通処理の再利用は継承よりコンポジション(多態を活用した委譲)を優先すべき場面が多い。また「クラス間の時系列のメッセージのやり取りを検証したいときはクラス図を使う」も誤り=時系列の協調はシーケンス図の役割であり、クラス図は静的構造の表現に用います。

カプセル化/継承/多態・UMLの図。
オブジェクトで設計する

3.2.4この節のまとめ

  • 多態を活用すれば新しい型の追加時に呼び出し側の既存コードを変更せずに済み、開放閉鎖原則(OCP)を満たしやすい
  • 依存性逆転原則(DIP)は上位モジュールが下位の具象実装ではなく抽象(インタフェース)に依存する設計、リスコフの置換原則(LSP)は子クラスが親の契約を破らないことを求める
  • 静的構造はクラス図、時系列の協調はシーケンス図、複雑な状態遷移ルールは状態遷移図で可視化する

進捗の記録にはログインが必要です。

理解度チェック

(軽い確認用)

Q1. 決済方式ごとの処理をif-else分岐で1メソッドに列挙しており、新しい決済方式を追加するたびにこのメソッドを直接書き換えている。この設計が違反している原則と、最も適切な改善方針はどれか。

Q2. PaymentProcessor(上位モジュール)が、決済方式ごとの具象クラス(下位モジュール)に直接依存せず、共通インタフェースPaymentMethod(抽象)に依存する設計に改めた。これが直接体現しているSOLID原則はどれか。

Q3. 決済処理オブジェクトが「未処理→処理中→完了」「未処理→処理中→失敗」のように複雑な状態遷移ルールを持ち、そのルール自体の妥当性を設計レビューで検証したい。最も適切なUML図はどれか。

理解度を確認第3章「ソフトウェア方式設計」の問題を解く