この記事は、保守性の高いコード設計シリーズの一部です。前回の記事「【第1部】 ifのネストはもうやめよう。ガード節で可読性を劇的に改善するテクニック」では、ガード節を使って
if
文のネストを解消する方法を学びました。
こんにちは。条件分岐には、if
文の他にswitch
文があります。特定の状態に応じて処理を振り分ける際に便利な構文ですが、このswitch
文がシステムの複数箇所で重複し始めると、それは危険信号です。仕様変更の際に修正漏れを引き起こし、追跡困難なバグの温床となります。
今回は、この**「重複するswitch
文」の問題を、オブジェクト指向の真髄とも言えるポリモーフィズムとストラテジーパターン**を用いて、いかにエレガントに解決するかを解説します。
switch
文の増殖が引き起こす悲劇
オンラインストアの配送方法を例に考えてみましょう。「通常配送」「お急ぎ便」「クール便」の3種類があるとします。
【Before】配送方法の種類(ShippingType
)に応じて、処理を切り替えるswitch
文
// 配送料を計算するメソッド
int calculateFee(ShippingType type) {
switch (type) {
case STANDARD: return 500;
case EXPRESS: return 800;
case REFRIGERATED: return 1200;
}
return 0; // default
}
// お届け日数を計算するメソッド
int getEstimatedDays(ShippingType type) {
switch (type) {
case STANDARD: return 5;
case EXPRESS: return 2;
case REFRIGERATED: return 3;
}
return 0; // default
}
ここまでは問題ないように見えます。しかし、ある日**「超お急ぎ便」**という新しい配送方法が追加されたらどうなるでしょう?
開発者は、calculateFee
メソッドにcase SUPER_EXPRESS:
を追加しました。しかし、getEstimatedDays
メソッドへの追加をうっかり忘れてしまいました。その結果、「超お急ぎ便」のお届け日数が0
日と計算されてしまい、顧客に誤った案内をしてしまうバグが発生します。
このように、同じ条件で分岐するswitch
文が複数箇所に分散していると、仕様変更時の修正漏れのリスクが非常に高くなります。
解決策:ストラテジーパターンで分岐をなくす
この問題を解決するのがストラテジーパターンです。これは、アルゴリズム(戦略)そのものをオブジェクトとして表現し、それらを交換可能にするデザインパターンです。
- 共通のインターフェースを定義する: まず、「配送料の計算」や「お届け日数の計算」といった、配送方法に共通の振る舞いをインターフェースとして定義します。
- 具体的な戦略クラスを作成する: 次に、「通常配送」「お急ぎ便」といった各種配送方法を、そのインターフェースを実装した具体的なクラスとして作成します。
- 移譲する: 最後に、
switch
文で処理を切り替える代わりに、対応する戦略クラスのオブジェクトに処理を**移譲(デリゲート)**します。
【After】ストラテジーパターンでswitch
文を撲滅したコード
ステップ1: インターフェースの定義
// 配送戦略インターフェース
interface ShippingStrategy {
int calculateFee();
int getEstimatedDays();
}
ステップ2: 具体的な戦略クラスの実装
class StandardShipping implements ShippingStrategy {
public int calculateFee() { return 500; }
public int getEstimatedDays() { return 5; }
}
class ExpressShipping implements ShippingStrategy {
public int calculateFee() { return 800; }
public int getEstimatedDays() { return 2; }
}
class RefrigeratedShipping implements ShippingStrategy {
public int calculateFee() { return 1200; }
public int getEstimatedDays() { return 3; }
}
ステップ3: switch
文をMapによる移譲に置き換える
class ShippingManager {
private final Map<ShippingType, ShippingStrategy> strategies;
ShippingManager() {
strategies = new HashMap<>();
strategies.put(ShippingType.STANDARD, new StandardShipping());
strategies.put(ShippingType.EXPRESS, new ExpressShipping());
strategies.put(ShippingType.REFRIGERATED, new RefrigeratedShipping());
}
// switch文が完全に消え、対応する戦略オブジェクトに処理を任せるだけになった
int calculateFee(ShippingType type) {
return strategies.get(type).calculateFee();
}
int getEstimatedDays(ShippingType type) {
return strategies.get(type).getEstimatedDays();
}
}
この設計では、「通常配送」に関するすべてのロジック(料金、日数など)はStandardShipping
クラスに凝集しています。
「超お急ぎ便」を追加したくなった場合、私たちはSuperExpressShipping
という新しいクラスを作り、strategies
のMap
に追加するだけです。既存のコードを修正する必要はなく、calculateFee
とgetEstimatedDays
の両方を実装し忘れるというミスも起こりません。
まとめ
重複するswitch
文は、低凝集と修正漏れのリスクを示す危険なサインです。 ストラテジーパターンを活用し、条件分岐そのものをオブジェクトに置き換えることで、以下のようなメリットが得られます。
- 高凝集: 関連するロジックが、一つのクラスにまとまる。
- 拡張性: 新しい条件(戦略)の追加が、新しいクラスの追加だけで済む(オープン・クローズドの原則)。
- 堅牢性: 仕様変更時の修正漏れのリスクが劇的に減少する。
switch
文を書きそうになったら、一度立ち止まって「これはポリモーフィズムで解決できないか?」と考えてみるのが、中級者への第一歩です。
▼次の記事 次回は、さらに複雑なビジネスルールを扱うための「ポリシーパターン」や、メソッドの意図を曖昧にする「フラグ引数」のリファクタリングなど、より高度な条件分岐の改善テクニックを探求します。