Java | オブジェクト指向:switch 文のオブジェクト化

Java Java
スポンサーリンク

「switch 文のオブジェクト化」とは何か

「switch 文のオブジェクト化」は、
switch にベタっと書いている「種類ごとの違い」を、
それぞれのクラス(オブジェクト)に持たせるように書き換えることです。

つまり、

switch で「この種類ならこうする、あの種類ならこうする」と分岐する
→ 種類ごとにクラスを作って、それぞれに自分の仕事を書いてもらう

という発想の転換です。

これをやると、

新しい種類を追加するときに既存の switch をいじらなくてよくなる
同じような switch があちこちにコピペされるのを防げる
コードの見通しが良くなる

といったメリットが出てきます。


典型的な悪い例:種別コード+switch だらけの計算

switch で種類ごとのロジックを全部書いてしまう

まず、よくあるパターンを見てみます。

final class ShippingFeeCalculator {

    int calculate(String shippingType, int weightGram) {
        switch (shippingType) {
            case "normal":
                return 500 + weightGram / 100;
            case "express":
                return 800 + weightGram / 50;
            case "economy":
                return 300;
            default:
                throw new IllegalArgumentException("unknown type: " + shippingType);
        }
    }
}
Java

一見シンプルですが、問題の種がいくつか埋まっています。

まず、「配送料の計算ルール」が ShippingFeeCalculator の switch に全部押し込められています。
もし、別の場所で「配送料をテキスト表示する」処理を書こうとすると、たいていこうなります。

String label(String shippingType) {
    switch (shippingType) {
        case "normal":  return "通常便";
        case "express": return "速達便";
        case "economy": return "格安便";
        default:        return "不明";
    }
}
Java

同じ "normal" / "express" / "economy" 判定が複数箇所に出てきます。
種類を追加・変更したくなったとき、
「全部の switch を探して直さないといけない」状態になります。

これが「switch 文だらけのコードが壊れやすい」ゆえんです。


オブジェクト指向的な発想:種類ごとのクラスに任せる

共通のインターフェースを定義する(重要)

まず、「配送種別は配送料を計算できる存在だ」という抽象をインターフェースで表します。

interface Shipping {
    int fee(int weightGram);
    String label();
}
Java

配送種別ごとに、このインターフェースを実装したクラスを作ります。

final class NormalShipping implements Shipping {

    @Override
    public int fee(int weightGram) {
        return 500 + weightGram / 100;
    }

    @Override
    public String label() {
        return "通常便";
    }
}

final class ExpressShipping implements Shipping {

    @Override
    public int fee(int weightGram) {
        return 800 + weightGram / 50;
    }

    @Override
    public String label() {
        return "速達便";
    }
}

final class EconomyShipping implements Shipping {

    @Override
    public int fee(int weightGram) {
        return 300;
    }

    @Override
    public String label() {
        return "格安便";
    }
}
Java

ここで大事なのは、
「normal なら〜、express なら〜」という分岐を switch に書くのではなく、
クラスそのものを分けてしまい、それぞれに自分のロジックを持たせたことです。

使う側は「switch を知らない」

利用側のコードはこう書けます。

Shipping shipping = new NormalShipping();
int fee = shipping.fee(1200);
String label = shipping.label();
Java

どの配送種別かによって、呼ばれるメソッドの中身が変わります。
つまり、switch で分岐する代わりに「オブジェクトの型ごとの振る舞いの違い(ポリモーフィズム)」に任せているわけです。

新しい配送種別(たとえば「バイク便」など)を追加したいときは、

BikeShipping implements Shipping

というクラスを新規追加するだけで良く、
既存の fee 呼び出し側には一切変更を入れなくて済みます。


「switch → オブジェクト化」の発想の流れ(深掘り)

1. switch が「何を基準に分岐しているか」を見つける

まず、自分のコードの中の switch を見て、「何で分岐しているか」を確認します。

文字列 "normal" / "express" / "economy"
enum の値
整数の種別コード

こうした「種類」を表す値で分岐しているなら、オブジェクト化のチャンスです。

2. その種類に「〜できる」という名前のメソッドを与えられないか考える

さきほどの例では、
「配送種別なら配送料を計算できる」「ラベルを返せる」
と考えました。

int fee(int weightGram);
String label();

のような「振る舞い」に名前を与え、それをインターフェースにします。
switch のそれぞれの case に書いていた処理を、その実装側に移すイメージです。

3. 呼び出し側から switch を追い出す

最終的には、呼び出し側のメソッドに switch がなくなる状態がゴールです。

「配送種別に応じて配送料を求める」メソッドが、
単に shipping.fee(weight) とだけ書かれている状態。
これが、「switch がオブジェクトに変わった」あとです。


enum を使った「オブジェクト化」もよく使う手

enum に振る舞いを持たせる書き方

Java では、enum 自体にメソッドを持たせることで「種類ごとの違い」を内側に閉じ込めることができます。

さきほどの配送種別を enum でやってみます。

enum ShippingType {

    NORMAL {
        @Override
        int fee(int weightGram) {
            return 500 + weightGram / 100;
        }

        @Override
        String label() {
            return "通常便";
        }
    },
    EXPRESS {
        @Override
        int fee(int weightGram) {
            return 800 + weightGram / 50;
        }

        @Override
        String label() {
            return "速達便";
        }
    },
    ECONOMY {
        @Override
        int fee(int weightGram) {
            return 300;
        }

        @Override
        String label() {
            return "格安便";
        }
    };

    abstract int fee(int weightGram);
    abstract String label();
}
Java

呼び出し側はこう書けます。

ShippingType type = ShippingType.NORMAL;
int fee = type.fee(1200);
String label = type.label();
Java

もしこれを enum ではなく switch でやっていたら、こうなります。

int fee(ShippingType type, int weightGram) {
    switch (type) {
        case NORMAL:  return 500 + weightGram / 100;
        case EXPRESS: return 800 + weightGram / 50;
        case ECONOMY: return 300;
        default: throw new IllegalArgumentException();
    }
}
Java

どちらが「種類ごとの責務」が enum 側にまとまっているかは、見れば分かると思います。
enum 版のほうが、「配送種別を追加・変更したときに見るべき場所」が明確です。


「switch をオブジェクト化すると何が嬉しいのか」もう一歩踏み込む

変更に強くなる

新しい種類を追加するとき、
switch 版では「全部の switch case を探して追加」する必要があります。
見落とせばバグです。

オブジェクト化すると、「新しいクラス(または enum 定数)を追加」して、
そこにだけ新しいロジックを書けばよくなります。

特に業務システムでは「種別が増える」「条件が増える」は日常茶飯事なので、
この違いが効いてきます。

同じ switch があちこちにコピペされなくなる

配送方法ごとに

料金計算
表示名
説明文
納期見積り

など、複数の振る舞いが必要になることがよくあります。

switch ベースだと、こういったメソッドごとに
switch (shippingType) がコピペされます。

オブジェクト化しておけば「配送種別が持つべき振る舞い」を
そのクラス/enum に集約できるので、ロジックがバラけなくなります。

テスト単位が小さくなる

例えば ExpressShipping クラス単体でテストできます。

fee をいくつかの weight でテスト
label が “速達便” になるかテスト

switch ベースだと、「いろんな type の組み合わせをまとめてテストする」ことになり、
テストケースの整理が大変になります。

オブジェクト化すれば、「種類ごとのテスト」と「それを使う側のテスト」を分けて考えられるので、
テストコードもすっきりします。


「いつでもオブジェクト化すべき?」への現実的な答え

正直に言うと、
「case が 2 つだけ、増える見込みもほぼない、処理も数行」
みたいな場面で、わざわざクラスを分けると逆に読みづらくなることもあります。

だから、何もかも機械的に「switch → クラスに分けろ」とは言いません。

オブジェクト化を強く検討すべきなのは、特に次のようなときです。

種類(case)が 3 つ以上あって、今後も増えそう
同じ switch が別メソッドや別クラスに複数回出てくる
「この種類ならこれを計算できる」といった“役割”がハッキリしている

こういうスイッチは、「臭い switch」です。
ちゃんと責務を持ったオブジェクトに変えてあげると、コード全体の健康度が一気に上がります。


まとめ:switch を見たら一度立ち止まるクセをつける

switch 文自体は悪くありません。
ただ、「種類ごとの振る舞い」を全部 switch に押し込めはじめると、

変更に弱い
ロジックが散らばる
テストがしづらい

という問題を抱え込みがちです。

switch を書いたとき、あるいは既存コードの switch を見たときに、
一度こう自問してみてください。

この switch は「種類ごとの役割の違い」を表していないか?
この種類自体に 〜できる というメソッドを持たせると、しっくりこないか?
同じような switch を他の場所でも書いていないか?

「しっくり来る」なら、それはオブジェクト化のサインです。

タイトルとURLをコピーしました