第8回(最終回)では、返品フローを追加してパターンの限界を検証します。そして、このパターンの正式名称を明かします。

前回の振り返り
前回はEU市場を追加し、OCPを確認しました。
- 既存のコードを変更せずに新市場を追加できた
- Open-Closed Principle(OCP)を満たす設計
- クラス数は14個に増加
今回は「新しい製品種」を追加した場合に何が起きるかを検証します。
この記事で学ぶこと
- 返品処理(新製品種)追加時の変更量を検証する
- OCPとSRPの衝突を理解する
- Abstract Factoryパターンの適用判断基準を学ぶ
新しい製品種の追加要件
ECサイトに返品機能を追加する要件が発生しました。
- 国内返品: 着払いで返送、返金3営業日
- 海外返品: 国際便で返送、返金10営業日
- EU返品: EU内便で返送、返金5営業日
各市場で返品処理が異なるため、Factoryに追加する必要があります。
返品クラスの実装
各市場向けの返品クラスを作成します。
| |
| |
| |
Factoryロールの変更
OrderFlowFactoryロールに新しいメソッドを追加する必要があります。
| |
全Factoryの改修
ロールに新しいメソッドを追加したため、すべてのConcreteFactoryに同じメソッドを実装する必要があります。
DomesticOrderFlowFactoryへの追加です。
| |
GlobalOrderFlowFactoryへの追加です。
| |
EUOrderFlowFactoryへの追加です。
| |
変更量の定量的評価
返品機能追加で発生した変更を定量化します。
| 追加/変更項目 | 数量 |
|---|---|
| 新規クラス(ReturnService) | 3個 |
| 変更ファイル(Factoryロール) | 1個 |
| 変更ファイル(ConcreteFactory) | 3個 |
| 追加メソッド | 4個(ロール1 + Factory3) |
| 追加行数(概算) | 約80行 |
EU市場追加との比較
前回(第7回)と今回の変更量を比較します。
| 追加内容 | 変更ファイル数 | 追加行数 | 影響クラス数 | 判定 |
|---|---|---|---|---|
| 新市場(EU)追加 | 0 | 約60行 | 0 | 軽い |
| 新製品種(返品)追加 | 4 | 約80行 | 4 | 重い |
新市場追加では既存コードを変更しませんでしたが、新製品種追加では4ファイルの修正が必要でした。
OCP vs SRPの衝突
この違いはSOLID原則の衝突によるものです。
OCP(Open-Closed Principle)について考えます。
- 新しいファミリ(市場)追加は容易
- 既存コードを変更せずに拡張できる
SRP(Single Responsibility Principle)について考えます。
- 新しい製品種追加で、全Factoryに責任が増える
- Factoryが「決済/配送/通知/返品」の4つの責任を持つようになる
flowchart LR
subgraph 新市場追加
direction TB
A1[EUFactory追加] --> A2[既存コード変更なし]
A2 --> A3[OCP準拠]
end
subgraph 新製品種追加
direction TB
B1[ReturnService追加] --> B2[全Factory変更必要]
B2 --> B3[OCP違反]
end
style A3 fill:#ccffcc
style B3 fill:#ffcccc
Abstract Factoryパターン
ここで、このシリーズで使ってきたパターンの名前を明かします。
「Abstract Factory」パターンです。
GoF(Gang of Four)が定義したデザインパターンの1つで、以下の特徴を持ちます。
- 関連するオブジェクト群(製品ファミリ)を一括生成する
- クライアントは抽象Factory経由で製品を取得し、具体クラスを知らない
- 新しいファミリ追加は容易だが、新しい製品種追加は重い
classDiagram
class AbstractFactory {
<<interface>>
+createProductA()
+createProductB()
}
class ConcreteFactory1 {
+createProductA()
+createProductB()
}
class ConcreteFactory2 {
+createProductA()
+createProductB()
}
class AbstractProductA {
<<interface>>
}
class AbstractProductB {
<<interface>>
}
AbstractFactory <|-- ConcreteFactory1
AbstractFactory <|-- ConcreteFactory2
ConcreteFactory1 ..> AbstractProductA
ConcreteFactory1 ..> AbstractProductB
ConcreteFactory2 ..> AbstractProductA
ConcreteFactory2 ..> AbstractProductB
適用判断フローチャート
Abstract Factoryパターンを使うべきかどうかの判断基準です。
flowchart TD
Q1{製品ファミリの一貫性が必要?<br>例: 決済+配送+通知をセット提供}
Q1 -- NO --> R1[Abstract Factoryは不要<br>Factory Methodで十分]
Q1 -- YES --> Q2
Q2{ファミリ種が増える可能性が高い?<br>例: 新しい市場が増える}
Q2 -- NO --> R2[Abstract Factoryは過剰<br>シンプルな分岐で十分]
Q2 -- YES --> Q3
Q3{製品種が安定している?<br>例: 決済/配送/通知が固定}
Q3 -- YES --> R3[Abstract Factory適用推奨]
Q3 -- NO --> R4[過剰設計のリスク高<br>Builder/Strategyを検討]
style R3 fill:#ccffcc
style R4 fill:#ffcccc
他パターンとの使い分け
| 状況 | 推奨パターン | 理由 |
|---|---|---|
| 製品ファミリの一貫性が必要 & 製品種が安定 | Abstract Factory | ファミリ追加はOCP準拠で容易 |
| 製品ファミリの一貫性が必要 & 製品種が不安定 | Builder | 段階的構築で柔軟に対応 |
| 製品ファミリの一貫性が不要 & 単一製品生成 | Factory Method | 継承ベースでシンプル |
| 既存オブジェクトの複製 | Prototype | 複製ベースで生成コスト削減 |
シリーズまとめ
このシリーズで学んだことを振り返ります。
- 国内注文処理をシンプルに実装した
- 海外対応でif/else分岐が増殖した
- 組み合わせミスで業務事故が発生した
- Abstract FactoryロールでインターフェースをAbstract Factoryで解決した
- 国内/海外Factoryで製品ファミリを一括生成した
- OrderProcessorでDIを実現した
- EU市場追加でOCPを確認した
- 返品フロー追加でパターンの限界を検証した
Abstract Factoryパターンは強力ですが、適用条件を見極めることが重要です。
- 製品ファミリの一貫性が業務上必須である
- ファミリ種(市場など)が増える可能性が高い
- 製品種(決済/配送など)が安定している
これらの条件がすべて揃った場合にのみ、Abstract Factoryパターンを検討してください。
最終的なクラス構成
classDiagram
class OrderFlowFactory {
<<Role>>
+create_payment()
+create_shipping()
+create_notification()
+create_return_service()
}
class DomesticOrderFlowFactory
class GlobalOrderFlowFactory
class EUOrderFlowFactory
class OrderProcessor {
+factory
+process()
}
OrderFlowFactory <|.. DomesticOrderFlowFactory
OrderFlowFactory <|.. GlobalOrderFlowFactory
OrderFlowFactory <|.. EUOrderFlowFactory
OrderProcessor --> OrderFlowFactory
まとめ
この記事では以下を学びました。
- 返品処理(新製品種)追加で全Factoryの改修が必要になった
- 新市場追加(OCP準拠)と新製品種追加(OCP違反)の違いを確認した
- Abstract Factoryパターンの名前と適用判断基準を学んだ
- 製品種が安定していることがパターン適用の重要条件
このシリーズを通じて、製品ファミリの一貫性を保つ設計パターンを体験しました。パターンの便利さと限界の両方を理解し、適切な場面で活用してください。
