首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 靜態工廠方法替代建構子:優缺點、命名慣例、實戰模式、Spring 整合

     編輯:Java 相關知識

前言

Effective Java 第 1 條建議:「考慮用靜態工廠方法取代建構子」。本文系統整理優缺點、標準命名慣例、六大實戰模式、Spring 整合技巧、效能影響與遷移清單,助你寫出更清晰、彈性、可測試的 API。

基本形式

public final class Boolean {
    // 私有建構子,強制使用工廠
    private Boolean(boolean value) { this.value = value; }

    public static Boolean valueOf(boolean b) {
        return b ? Boolean.TRUE : Boolean.FALSE; // 回傳快取單例
    }
}

五大優點

優點說明範例
1. 有名稱建構子名固定為類別名,工廠可語意化BigInteger.probablePrime() vs new BigInteger()
2. 不必每次建立新物件回傳快取/單例/池化物件,減少 GC 壓力Boolean.valueOf(), Integer.valueOf(-128~127)
3. 可回傳子類型介面/抽象類別隱藏具體實作,支援多型Collections.emptyList() 回傳 EmptyList 子類
4. 參數化型別推導簡潔鑽石運算子 <> 配合工廠,減少泛型囉嗦Map.of(), List.of() (JDK 9+)
5. 服務提供者框架 (SPI)JDBC DriverManager.getConnection()、ServiceLoader動態發現實作類別

兩大缺點

  1. 無法子類化:若建構子皆私有,子類別無法呼叫 super()。解法:提供 protected 建構子或 @VisibleForTesting 套件私有建構子。
  2. 難以被 IDE/框架發現:反序列化、DI 容器、建構器注入可能找不到。解法:@JsonCreator、@Autowired 標註工廠、或保留一個公共建構子供框架用。

標準命名慣例 (Google/Java 標準)

慣例用途範例
from單一參數型別轉換Date.from(instant)
of多參數聚合/值物件建立EnumSet.of(e1, e2), LocalDate.of(y,m,d)
valueOffrom + of 綜合、型別轉換Integer.valueOf("123")
getInstance單例/共享實例Calendar.getInstance()
newInstance每次建立新實例 (如 Builder)ArrayList.newInstance()
getType回傳不同類型 (工廠在不同類別)FileSystems.getDefault()
newType建立新實例 (工廠在不同類別)Files.newBufferedReader()

六大實戰模式

1. 快取/池化 (Immutable 小物件)

public final class Color {
    private static final Map CACHE = new ConcurrentHashMap<>();
    private final int rgb;
    private Color(int rgb) { this.rgb = rgb; }
    public static Color of(int r, int g, int b) {
        int key = (r << 16) | (g << 8) | b;
        return CACHE.computeIfAbsent(key, Color::new);
    }
}

2. 介面隱藏實作 (SPI 風格)

public interface ConnectionPool {
    Connection borrow();
    void release(Connection c);
    static ConnectionPool create(String url, Props props) {
        return switch (props.get("type")) {
            case "hikari" -> new HikariPool(url, props);
            case "dbcp" -> new DbcpPool(url, props);
            default -> throw new IllegalArgumentException();
        };
    }
}

3. Builder 配合工廠 (複雜物件)

public final class HttpRequest {
    private final String url, method, body;
    private HttpRequest(Builder b) { this.url = b.url; this.method = b.method; this.body = b.body; }
    public static Builder builder() { return new Builder(); }
    public static class Builder {
        private String url, method = "GET", body;
        public Builder url(String u) { this.url = u; return this; }
        public Builder method(String m) { this.method = m; return this; }
        public Builder body(String b) { this.body = b; return this; }
        public HttpRequest build() { return new HttpRequest(this); }
    }
    // 便利工廠
    public static HttpRequest get(String url) { return builder().url(url).build(); }
    public static HttpRequest post(String url, String body) { return builder().url(url).method("POST").body(body).build(); }
}

4. 參數驗證與失敗快速

public static User create(String email, String rawPassword) {
    if (!EmailValidator.valid(email)) throw new IllegalArgumentException("Invalid email");
    if (rawPassword.length() < 8) throw new IllegalArgumentException("Password too short");
    return new User(email, PasswordEncoder.encode(rawPassword));
}

5. 多型回傳 (介面/抽象類別)

public interface List {
    static  List of() { return new ImmutableCollections.List0<>(); }
    static  List of(E e1) { return new ImmutableCollections.List1<>(e1); }
    static  List of(E e1, E e2) { return new ImmutableCollections.List2<>(e1, e2); }
    // ... 實際回傳不同具體子類別
}

6. 可選依賴/條件建立

public static MetricsReporter create(Environment env) {
    if (env.getProperty("datadog.enabled", Boolean.class, false)) {
        return new DatadogReporter(env);
    }
    return new LoggingReporter(); // 預設無操作
}

Spring 整合最佳實踐

@Configuration
public class AppConfig {
    // 靜態工廠方法建立 Bean
    @Bean
    public DataSource dataSource(DataSourceProperties props) {
        return DataSourceBuilder.create()
            .url(props.getUrl())
            .username(props.getUsername())
            .password(props.getPassword())
            .type(HikariDataSource.class)
            .build(); // 實際上 DataSourceBuilder 是 Builder 模式
    }

    // 實例工廠方法 (非靜態)
    @Bean
    public ObjectMapper objectMapper(JacksonProperties props) {
        return JacksonConfig.createObjectMapper(props); // 靜態工廠
    }
}

// 元件掃描時,建構子注入優先;若類別只有私有建構子 + 靜態工廠
// 需在 @Configuration 類別中手動 @Bean,或使用 @Component + 靜態工廠不支援自動裝配
@Component
public final class ExpensiveService {
    private ExpensiveService() {} // 私有
    public static ExpensiveService create(Config cfg) { return new ExpensiveService(); }
}
// 此時 Spring 無法自動 @Autowired ExpensiveService,需手動 @Bean

效能影響

  • 逃逸分析:JIT 可將工廠方法內聯,消除物件分配 (標量替換),效能等同建構子。
  • 快取命中:Integer.valueOf、Boolean.valueOf 避免分配,顯著降低 GC。
  • 反射開銷:框架若用反射呼叫工廠,比直接 new 慢;建議保留單一公共建構子供框架用,業務碼用工廠。

遷移清單 (建構子 → 靜態工廠)

  1. 將建構子改為 private 或 package-private。
  2. 新增 public static Type of(...) / valueOf(...)。
  3. 檢查所有 new Type(...) 呼叫點,改為 Type.of(...)。
  4. 若有子類別,提供 protected 建構子或 @VisibleForTesting。
  5. 為序列化/框架保留無參建構子或加 @JsonCreator 標註工廠。
  6. 更新 Javadoc,標註「請使用 of() 建立實例」。
  7. 執行完整測試套件,確認行為一致。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved