EffectiveJava-考虑使用静态工厂方法代替构造函数

静态工厂 枚举 Java

Posted by lvyonghao on April 4, 2024

考虑使用静态工厂方法代替构造函数

本节介绍Effective-Java中,第一条:使用静态工厂方法替代构造函数。

简介

传统方法获取实例的方式是使用默认的构造函数,另一种技术是使用公共的静态工厂方式。这是书中提供的一个简单的demo,将原始的布尔类型转为布尔对象的装箱类。

public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}

优点

1. 静态工厂它可以有自己的名字, 比如这个getInstance:

StackWalker luke = StackWalker.getInstance();

2. 静态工厂可以返回一个不可变的类,不需要每次调用的时候都返回一个新的对象,比如单例模式:

public class AppConfig {
    // 私有静态的唯一实例
    private static final AppConfig INSTANCE = new AppConfig();

    // 私有构造器,防止外部 new
    private AppConfig() {
        // 初始化配置
    }

    // 公有的静态工厂方法
    public static AppConfig getInstance() {
        return INSTANCE;
    }

    // 示例方法
    public String getAppName() {
        return "My Awesome App";
    }
}

3. 和构造函数不同,可以返回类型的任何子类型对象。我们定义一个接口Fruit,然后通过静态工厂方法来返回不同的子类。

public interface Fruit {
    String getName();
}

然后实现两个子类:

public class Apple implements Fruit {
    public String getName() {
        return "Apple";
    }
}

public class Banana implements Fruit {
    public String getName() {
        return "Banana";
    }
}

使用静态工厂类决定构造什么子类

public class FruitFactory {
    public static Fruit createFruit(String type) {
        if ("apple".equalsIgnoreCase(type)) {
            return new Apple();
        } else if ("banana".equalsIgnoreCase(type)) {
            return new Banana();
        } else {
            throw new IllegalArgumentException("Unknown fruit: " + type);
        }
    }
}

4. 你可以先定义一个工厂接口或静态工厂类,但返回的实现类可以等到以后才添加(比如通过配置、反射、插件等机制注入),实现 运行时绑定 / 插件式开发 / 解耦部署。

定义一个接口

public interface PaymentProcessor {
    void pay(int amount);
}

提供一个静态工厂方法,ServiceLoader用来加载或查找所有实现了PaymentProcessor接口类型的实例。

public class PaymentFactory{
    public static Payment getPaymentProceessor(){
        ServiceLoader<PaymentProcessor> loader = ServiceLoader.load(PaymentProcessor.class);
        for (PaymentProcessor p : loader) {
            return p; // 返回第一个发现的实现类
        }
    }
}

后来,当你新建了另一个模块实现了接口:

public class StripePayment implements PaymentProcessor {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " via Stripe.");
    }
}

你有以下的配置

# META-INF/services/PaymentProcessor
com.example.StripePayment

运行时调用

PaymentProcessor processor = PaymentFactory.getPaymentProcessor();
processor.pay(99);

这段代码并不依赖于StripePayment类的实现,它可以先定义,返回类(StripePayment)可以后面再添加。使用者不用关心你实际返回哪个类,只要用接口即可,实现了解耦和可扩展。


缺点

如果一个类只提供私有构造函数 + 静态工厂方法(没有 public 或 protected 构造器),那么它就不能被继承。换老老话说就是子类不能使用父类的构造函数。当然缺点的另一面也是他的优点,更鼓励组合而不是继承。