昨天我們透過策略模式(Strategy Pattern)成功消除了深層的 if-else 條件地獄。但在物件導向開發中,除了「行為型模式」,另一個極容易引發混亂的是「建立型模式(Creational Patterns)」。
在專案剛起步時,我們常直接在各個業務 Controller 或 Service 中隨手 new ConcreteObject()。但隨著專案規模變大,物件的建構參數愈來愈複雜、甚至需要根據環境配置動態決定生成哪種實例時,滿地散落的 new 就會造成極高的耦合度。
今天我們就來示範:如何讓 ChatGPT 扮演物件導向設計導師,一步步將分散且硬編碼(Hard-coded)的物件建構邏輯,重構成優雅的「工廠模式(Factory Pattern)」與「執行緒安全的單例模式(Singleton Pattern)」!
當你發現系統中物件的建立邏輯開始變得臃腫、需要抽離時,可以使用以下提問結構:
【設計模式重構提示詞範本】
請扮演資深 Java 軟體架構師。以下是我目前建立與管理物件的實作片段:
[貼上既有的代碼]
請依序進行架構指導:
new 或粗糙實例化會造成哪些維護與單元測試(Mocking)上的問題?假設我們有一個通知模組,需要根據使用者偏好發送「Email、SMS 簡訊、或 Slack Webhook」:
[重構前:業務邏輯與建構細節強耦合]
public class NotificationService {
public void notifyUser(String channel, String recipient, String message) {
if ("EMAIL".equalsIgnoreCase(channel)) {
// 需要配置 SMTP host、port、auth 認證資訊
EmailSender sender = new EmailSender("smtp.office365.com", 587, "user", "pass");
sender.send(recipient, message);
} else if ("SMS".equalsIgnoreCase(channel)) {
// 需要配置電信商 API key 與發送端代號
SmsSender sender = new SmsSender("API_KEY_98765", "TAIWAN_TELECOM");
sender.send(recipient, message);
} else {
throw new IllegalArgumentException("未支援的通知渠道: " + channel);
}
}
}
痛點:每次呼叫都要重新 new 一次、配置參數散落各處,且呼叫端必須清楚知道每個 Sender 需要哪些參數。
AI 會帶領我們拆解為三層架構:
[共用介面定義]
public interface MessageSender {
void send(String recipient, String message);
}
public class EmailSender implements MessageSender {
public void send(String recipient, String message) {
System.out.println("【Email】寄送給 " + recipient + ",內容:" + message);
}
}
public class SmsSender implements MessageSender {
public void send(String recipient, String message) {
System.out.println("【SMS】簡訊傳送給 " + recipient + ",內容:" + message);
}
}
AI 推薦使用靜態內部類別(Static Inner Class)方式實作單例模式。這種做法不需使用 synchronized 關鍵字,既保證了執行緒安全,又能享有延遲加載(Lazy Initialization)的效能優勢:
[工廠類別與安全單例實作]
import java.util.HashMap;
import java.util.Map;
public class NotificationFactory {
private final Map<String, MessageSender> senderRegistry = new HashMap<>();
// 私有建構子,防止外部隨意 new
private NotificationFactory() {
// 在工廠內部統一管理複雜的建構與參數注入
senderRegistry.put("EMAIL", new EmailSender());
senderRegistry.put("SMS", new SmsSender());
}
// Bill Pugh 靜態內部類別單例實作
private static class SingletonHelper {
private static final NotificationFactory INSTANCE = new NotificationFactory();
}
public static NotificationFactory getInstance() {
return SingletonHelper.INSTANCE;
}
// 工廠獲取方法
public MessageSender getSender(String channel) {
MessageSender sender = senderRegistry.get(channel.toUpperCase());
if (sender == null) {
throw new IllegalArgumentException("不支援的通知頻道:" + channel);
}
return sender;
}
}
現在,我們的商業邏輯類別變得無比清爽:
[重構後簡潔的業務代碼]
public class NotificationService {
public void notifyUser(String channel, String recipient, String message) {
// 透過單例工廠取得發送器,完全不用關心它是如何被 new 出來的
MessageSender sender = NotificationFactory.getInstance().getSender(channel);
sender.send(recipient, message);
}
}
new 一個,在高併發時會引發頻繁的記憶體配置與 GC。配合單例與快取註冊表,能大幅降低伺服器負載。MessageSender 或將 Mock 物件註冊進工廠,不需要模擬真實的外部 SMTP / SMS 伺服器連線。今天我們透過 AI 的逐步引導,學會了如何運用工廠模式抽離物件建構細節,並透過 Java 靜態內部類別實現最優雅、高效的執行緒安全單例模式。