整理 Effective Java 書中 Item 23: Prefer class hierarchies to tagged classes 心得筆記

主旨

在寫Java時,有時我們會想設計一個類別,可以代表「好幾種不同型態的物件」。這時,有些人會選擇用一個「tag 欄位」來做判斷,稱為 tagged class。但這樣的寫法其實有很多問題,本條目會說明為什麼應該用「類別階層」來取代「tagged class」。

🧨 什麼是 Tagged Class?

來看一個實際範例:

// 不推薦的 tagged class 寫法
class Notification {
    enum Type { EMAIL, SMS }

    final Type type;
    String emailAddress;
    String phoneNumber;
    String message;

    Notification(String emailAddress, String message) {
        this.type = Type.EMAIL;
        this.emailAddress = emailAddress;
        this.message = message;
    }

    Notification(String phoneNumber, String message, boolean isSMS) {
        this.type = Type.SMS;
        this.phoneNumber = phoneNumber;
        this.message = message;
    }

    void send() {
        switch (type) {
            case EMAIL:
                System.out.println("Sending EMAIL to " + emailAddress + ": " + message);
                break;
            case SMS:
                System.out.println("Sending SMS to " + phoneNumber + ": " + message);
                break;
        }
    }
}

這樣的寫法有什麼問題呢?

  • 有些欄位會根本用不到(例如 SMS 不會用到 emailAddress
  • send() 要用 switch 判斷型別,之後多加一種通知(像是 Push)也要改這邊
  • 新增或移除一種通知方式會牽動整個類別
  • 這個類別過於雜亂,維護困難,未來很容易出錯

✅ 改用類別階層的寫法

Java 是物件導向語言,這種情況就應該用「繼承」來處理不同型態的資料。

abstract class Notification {
    String message;
    Notification(String message) {
        this.message = message;
    }
    abstract void send();
}
class EmailNotification extends Notification {
    String emailAddress;

    EmailNotification(String emailAddress, String message) {
        super(message);
        this.emailAddress = emailAddress;
    }

    @Override
    void send() {
        System.out.println("Sending EMAIL to " + emailAddress + ": " + message);
    }
}
class SMSNotification extends Notification {
    String phoneNumber;

    SMSNotification(String phoneNumber, String message) {
        super(message);
        this.phoneNumber = phoneNumber;
    }

    @Override
    void send() {
        System.out.println("Sending SMS to " + phoneNumber + ": " + message);
    }
}

✅ 有哪些好處?

  • 每個類別只負責自己的邏輯,清楚乾淨
  • 加新功能(像是 PushNotification)時只要加一個新的類別,不需要動到其他程式碼
  • 所有欄位都可以設成 final,更安全
  • 編譯器會幫你檢查有沒有實作抽象方法,不會發生漏寫的錯誤

🧠 真實世界例子

想像你在設計一個「動物」系統,一開始只有「狗」和「貓」,你可能會這樣寫:

class Animal {
    enum Type { DOG, CAT }
    final Type type;
    String barkSound;
    String meowSound;
}

這會讓你日後加入「牛」或「羊」變得很痛苦。

如果改用類別階層,每種動物就是一個子類別,各自定義自己的叫聲方法,程式碼清楚、擴充性也高。

📝 小結

Tagged Class = 如果裡面要靠欄位 enum 來區分子型別,那就是警訊!

  • 如果你寫了很多 switch (tag) 的 code,請馬上停下來。
  • 改用「類別階層 + 抽象方法」不只更乾淨、安全、擴充性也高。
  • 設計擴充時要靠 compiler 幫你檢查,而不是全靠自己記住要改哪些地方。

當你下次再看到有 enum 搭配一堆 ifswitch 的設計,請想一想:「這是不是該用 class hierarchy 解決?」