Effective Java笔记:静态工厂方法代替构造器

静态工厂方法代替构造器是《Effective Java》(第3版) 中非常重要的一条建议,** 避免直接使用构造器,优先考虑静态工厂方法**。这是编写高质量、易维护、灵活代码的一个关键技巧。


静态工厂方法是什么?

静态工厂方法是通过 类的静态方法 来创建实例,而不是直接通过构造器创建对象。

示例说明传统构造器与静态工厂方法的对比:

1. 使用构造器

public class User {
    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

调用方式 → 标准构造器:

User user = new User("Alice", 30);

2. 使用静态工厂方法

public class User {
    private final String name;
    private final int age;

    // 私有构造器,限制直接创建对象
    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 静态工厂方法用于创建实例
    public static User of(String name, int age) {
        return new User(name, age);
    }
}

调用方式 → 静态方法:

User user = User.of("Alice", 30);

与构造器创建看起来相似,但静态工厂方法有很多优点。


静态工厂方法的优势

书中强调:与直接使用构造器相比,静态工厂方法提供了更大的灵活性和可维护性。以下是具体优点:


1. 可以为方法命名,提高代码可读性

  • 构造器只能通过类名进行标识,但静态工厂方法可以取一个有意义的名字,描述返回对象的意图。

比较两种代码:

BigInteger(int bitLength, int certainty, Random rnd);  // 构造器
BigInteger.probablePrime(int bitLength, Random rnd);  // 静态工厂方法

probablePrime 作为名字明确表达了工厂方法的真正意图,因此更加直观。而构造器 BigInteger(int bitLength, ...) 则没有这样的语义信息。


2. 可以控制实例的数量,返回单例或缓存实例

  • 通过静态工厂方法,有机会缓存已经创建的实例,比如返回 单例模式对象复用对象

单例示例:

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() { }  // 私有构造器

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

调用:

Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);  // true,始终返回同一个实例

此外,还可以实现对象池(缓存):

public class User {
    private static final Map<String, User> CACHE = new HashMap<>();

    private final String name;

    private User(String name) {
        this.name = name;
    }

    public static User of(String name) {
        return CACHE.computeIfAbsent(name, User::new);
    }
}

3. 可以返回子类或特定实现对象

  • 静态工厂方法不需要返回该类的对象,可以返回任意子类的实例,这样能有效隐藏实现细节。

示例:

public interface Car {
    void drive();
}

class ElectricCar implements Car {
    @Override
    public void drive() {
        System.out.println("Electric car driving...");
    }
}

class GasCar implements Car {
    @Override
    public void drive() {
        System.out.println("Gas car driving...");
    }
}

class CarFactory {
    public static Car getCar(String type) {
        return switch (type) {
            case "electric" -> new ElectricCar();
            case "gas" -> new GasCar();
            default -> throw new IllegalArgumentException("Unknown car type");
        };
    }
}

调用:

Car car = CarFactory.getCar("electric");
car.drive();  // Electric car driving...

优点:调用者不需要知道具体类型,只关注接口或父类。可以轻松复用接口实现,且隐藏子类,增强了灵活性。


4. 延迟实例化,提高性能

  • 静态工厂方法可以根据需要控制实例化。

例如,Boolean.valueOf() 方法返回了 Boolean.TRUEBoolean.FALSE,确保所有使用者共享同一实例,而不是每次创建新的对象:

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

这种直接复用极大地提高了性能。


5. 灵活定制返回结果类型

  • 静态工厂方法可以根据参数灵活地返回不同的类型实例,这种灵活性是构造器无法实现的。

例如,返回不可变集合:

List<String> list = List.of("A", "B", "C");
Set<Integer> set = Set.of(1, 2, 3);

Java 标准库中的例子

  • Collections.unmodifiableList(...) 返回的是不可变视图对象,防止对原集合的修改。

6. 适合参数类型复杂的情况

  • 使用静态工厂方法时,我们可以通过不同的方法名称将参数组合简化,不需要多个类似重载的构造器。

传统构造器的缺点

public class User {
    private String name;
    private int age;
    private boolean isAdmin;

    public User(String name) { ... }
    public User(String name, int age) { ... }
    public User(String name, int age, boolean isAdmin) { ... }
}

重载构造器太多,容易混淆。

替代方案

public class User {
    private String name;
    private int age;
    private boolean isAdmin;

    private User(String name, int age, boolean isAdmin) {
        this.name = name;
        this.age = age;
        this.isAdmin = isAdmin;
    }

    public static User ofName(String name) {
        return new User(name, 0, false);
    }

    public static User ofNameAndAge(String name, int age) {
        return new User(name, age, false);
    }

    public static User admin(String name, int age) {
        return new User(name, age, true);
    }
}

静态工厂方法的缺点

当然,静态工厂方法也有一些不足之处:

  1. 难以发现:相比构造器,静态工厂方法不能靠 “new” 关键字直观发现,需通过文档了解其用法。

    • 解决方案:为静态工厂方法起一个清晰的名字,让方法意图更明显。
  2. 无法被子类继承:因为静态方法不能被子类重写,可能会影响扩展性。

    • 解决方案:将类设计为 final 或提供更高层次的抽象(接口 + 工厂类)。

在软件开发中,是否将类设计为 final 或通过更高层次的抽象解决扩展问题,往往取决于具体需求和设计情境。这两种方法各有适用场景,下面用一个更具体的例子来讲解这两种方式的使用:


方案 1:将类设计为 final
意图:

通过将类设计为 final,防止子类继承或改写它的行为。这种做法适用于工具类、不可变类或那些不希望被继承和扩展的类。

示例代码:
public final class PaymentService {
    private final String paymentMethod;

    // 私有构造器
    private PaymentService(String paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    // 静态工厂方法:创建具体支付服务
    public static PaymentService createAliPayService() {
        return new PaymentService("AliPay");
    }

    public static PaymentService createWeChatPayService() {
        return new PaymentService("WeChatPay");
    }

    public void processPayment(double amount) {
        System.out.println("Processing " + paymentMethod + " payment of $" + amount);
    }
}
使用方式:
PaymentService alipay = PaymentService.createAliPayService();
alipay.processPayment(100.00);

PaymentService weChatPay = PaymentService.createWeChatPayService();
weChatPay.processPayment(200.00);
特点:
  1. final 修饰:类不可继承,确保其行为是固定的(如安全性保证、防止修改,特别适合工具类)。
  2. 静态工厂方法:屏蔽了具体构造过程,调用者只能通过方法名选择对象类型(比如返回支付宝或微信支付实例)。
  3. 局限性
    • 一旦定义了 final,就无法扩展。如果需要追加新的支付方式(例如 PayPal),需要在原类中添加新的静态方法并修改类代码。

方案 2:接口 + 工厂类
意图:

提供一个更高层次的抽象,通过接口定义行为,由工厂类动态决定返回具体的实现。这种设计更灵活,易于维护和扩展。

示例代码:
1. 定义接口:
public interface PaymentService {
    void processPayment(double amount);
}
2. 提供具体实现类:
public class AliPayService implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing AliPay payment of $" + amount);
    }
}

public class WeChatPayService implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing WeChatPay payment of $" + amount);
    }
}

public class PayPalService implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of $" + amount);
    }
}
3. 创建工厂类:
public class PaymentServiceFactory {
    public static PaymentService getPaymentService(String type) {
        return switch (type.toLowerCase()) {
            case "alipay" -> new AliPayService();
            case "wechatpay" -> new WeChatPayService();
            case "paypal" -> new PayPalService();
            default -> throw new IllegalArgumentException("Unknown payment type: " + type);
        };
    }
}
使用方式:
PaymentService alipay = PaymentServiceFactory.getPaymentService("alipay");
alipay.processPayment(100.00);

PaymentService weChatPay = PaymentServiceFactory.getPaymentService("wechatpay");
weChatPay.processPayment(200.00);

PaymentService paypal = PaymentServiceFactory.getPaymentService("paypal");
paypal.processPayment(300.00);
特点:
  1. 接口抽象:调用方无需关心具体类的实现(AliPayServiceWeChatPayService 等),只与接口 PaymentService 交互。这使得代码更具扩展性。

  2. 工厂动态实例化:工厂类 PaymentServiceFactory 通过传入的参数,动态决定返回哪个具体实现。

  3. 易扩展:添加新的支付方式,只需新增实现类和修改工厂逻辑(而不需要修改已有类的代码):

    新增一个支付方式(例如 StripeService):

    public class StripeService implements PaymentService {
        @Override
        public void processPayment(double amount) {
            System.out.println("Processing Stripe payment of $" + amount);
        }
    }
    
    // 修改工厂类:
    public static PaymentService getPaymentService(String type) {
        return switch (type.toLowerCase()) {
            case "alipay" -> new AliPayService();
            case "wechatpay" -> new WeChatPayService();
            case "paypal" -> new PayPalService();
            case "stripe" -> new StripeService();
            default -> throw new IllegalArgumentException("Unknown payment type: " + type);
        };
    }
    

两种方案的对比
特点final 类方案接口 + 工厂类方案
扩展性不可扩展,新增支付方式需要修改原类非常灵活,添加新实现无需修改已有代码
适用场景行为固定,稳定,不希望子类扩展行为多样,依赖多种实现,鼓励接口分层设计
复杂性较低,适合简单场景实现较高,对接更多实现类和分层需求
设计模式静态工厂工厂模式
子类限制不允许子类继承调用方只关心接口,无需了解具体的实现类

总结和应用建议
  1. 使用 final 类:

    • 如果当前类是小型工具类(如 StringUtilsMath),或对外暴露的是一个行为固定、简单且无需扩展的类,使用 final 关键字能防止滥用继承,同时提升安全性。
    • 适合进行稳定行为封装,如单例模式或者不可变对象。
  2. 使用接口 + 工厂类:

    • 若系统需求复杂且容易变化(如支持多种支付方式、插件化架构等),推荐使用接口加工厂类的模式。利用接口定义行为、工厂管理创建过程,实现解耦和扩展性。
    • 遵循 “面向接口编程” 的原则,便于后期维护和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值