Effective Java —— 用静态工厂方法代替构造器

本文参考

本篇文章参考自《Effective Java》第三版第一条"Consider static factory methods instead of constructors"

另外参考了其它几篇文章的解读:

https://www.cnblogs.com/dyj-blog/p/8867028.html

https://blog.csdn.net/u014129886/article/details/89670049

前言

第一条的篇章来自"Creating and Destroying Objects",讲述的是何时并且如何创建对象,销毁对象,所以第一条的"静态工厂方法"不同于软件设计模式中的"工厂方法"(Factory Method Pattern),二者没有任何直接的关联

First Advantage —— they have names

构造方法的方法名始终与类名相同,当我们有不同的构造方法时,就需要保证不同的构造方法有不同的形参类型、形参个数以及形参顺序,以此实现构造方法的重载

原文将这种构造方式称为"a really bad idea",因为当构造方法增多时,显然我们难以判断每个构造方法将会构造怎样的实例,我们需要为每个构造方法书写清晰准确的代码注释,防止选择了错误的构造方法构造实例

但是静态工厂方法不同于构造方法,它的方法名不受类名的约束,我们可以根据返回的实例的具体用途或具体特征来设计方法名,原文举了一个BigInteger的一个例子如下:

For example, the constructor BigInteger(int, int, Random), which returns a BigInteger that is probably prime, would have been better expressed as a static factory method named BigInteger.probablePrime.

也因为静态工厂方法可以拥有自己独特的名字,所以我们可以在不同的静态工厂方法中调用相同方法签名的构造方法,不过为此构造方法传入了不同的实参

 

Second Advantage —— they are not required to create a new object each time they're invoked

从上面一个Advantage来看,我们需要在静态工厂方法内调用构造方法,实际上我们可以直接返回一个静态成员字段,这个字段就是这个类的实例

静态成员字段随类的装载便进行构建,也可以在第一次被用到时才进行实例化,这分别对应了单例模式下的饿汉模式和懒汉模式

当然我们也可以使用Map或List等数据结构来缓存实例化后的对象,当再次调用静态工厂方法时,可以直接从集合中进行获取

构造方法显然无法完成这一点,我们每次使用new关键字都会产生一个新的实例化对象

原文称这种静态工厂方法的解决方案为"instance-controlled"

Instance control allows a class to guarantee that it is a singleton or noninstantiable. Also, it allows an immutable value class to make the guarantee that no two equal instances exist: a.equals(b) if and only if a == b. This is the basis of the Flyweight pattern.(享元模式)

 

Third Advantage —— they can return an object of any subtype of their return type

构造方法只能返回本类的实例化对象,但是静态工厂方法能够返回本类的子类的实例化对象,提供更好的灵活性

我们还可以将子类的权限修饰符设置为private,从而精简API并隐藏更多的类实现信息

原文称这种静态工厂方法的解决方案为"interface-based framework"

One application of this flexibility is that an API can return objects without making their classes public. Hiding implementation classes in this fashion leads to a very compact API. This technique lends itself to interface-based frameworks, where interfaces provide natural return types for static factory methods.

 

Fourth Advantage —— the class of the returned object can vary from call to call as a function of the input parameters

根据静态工厂方法的参数值不同,返回的对象的类也可以不同,这一条Advantage和上一条Advantage有一点相似之处, 如静态工厂方法所处的类有多个子类,那么在静态工厂方法内可以根据不同的条件返回不同的子类实例

我们用EnumSet抽象类作例,EnumSet抽象类有两个子类RegularEnumSet和JumboEnumSet,后者支持存储超过64个元素,前者只能存储64个以内,在EnumSet的noneOf()方法中,会根据Enum长度的不同,返回不同的子类

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    Enum<?>[] universe = getUniverse(elementType);
    if (universe == null)
        throw new ClassCastException(elementType + " not an enum");
    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}

 

Fifth Advantage —— the class of the returned object need not exist when the class containing the method is written

方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

原文称这种静态工厂方法的解决方案为"service provider frameworks",它有如下四个组件:

  • service interface -> represents an implementation of service
  • service provider interface (optional) -> describes a factory object that produce instances of the service interface
  • service access API -> clients use it to obtain instances of the service
  • provider registration API -> providers use it to register implementations (service interface or service provider interface)

若用Jdbc作例,上述四个组件对应如下:

  • service interface -> interface Connection
  • service provider interface -> interface Driver
  • service access API -> DriverManager.getConnection()
  • provider registration API -> DriverManager.registerDriver()

他们的作用分别为:

  • Connection接口具备连接数据库的服务
  • Driver接口的connect()方法返回Connection服务接口实例,是对Connection接口的又一层封装
  • 用户使用DriverManager.getConnection()方法获取连接数据库的服务
  • 服务提供者使用DriverManager.registerDriver()注册Driver实例

注意,从Java 6开始,Java提供了ServiceLoader服务提供者框架,不需要我们自行实现上面四个组件

Since Java 6, the platform includes a general-purpose service provider framework, java.util.ServiceLoader, so you needn't, and generally shouldn't, write your own (Item 59). JDBC doesn't use ServiceLoader, as the former predates the latter.

 

First Disadvantage —— classes without public or protected constructors cannot be subclassed

因为实例通过静态工厂方法返回,构造方法的权限修饰符通常设置为private,所以该类不可被继承

我们可以通过复合来解决这个缺陷

Arguably this can be a blessing in disguise because it encourages programmers to use composition instead of inheritance, and is required for immutable types.

 

Second Disadvantage —— they are hard for programmers to find

在Java doc中,构造方法的描述在比较靠前的位置,因此比较容易查阅,但是静态工厂方法的描述不仅比较靠后,而且和普通的静态方法混杂在一起,导致我们难以区分

我们可以通过统一命名规范来解决这个问题

  • from() -> 类型转换方法,它只有单个参数,返回该类型的一个相对应的实例
  • of() -> 聚合方法,带有多个参数,返回该类型的一个实例,把它们合并起来
  • valueOf() -> 比 from 和 of 更烦琐的一种替代方法
  • instance() 或者 getInstance() -> 返回的实例是通过方法的(如有)参数来描述,但是不能说与参数具有同样的值
  • create()或者 newInstance() -> 像instance 或者 getInstance 一样,但 create 或者 newInstance 能够确保每次调用都返回一个新的实例
  • getType() -> 像getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type 表示工厂方法所返回的对象类型
  • newType() -> 像newInstance 一样,但是在工厂方法处于不同的类中的时候使用。Type 表示工厂方法所返回的对象类型
  • type() -> getType 和 newType 的简化版
posted @ 2020-04-20 22:30  咕~咕咕  阅读(247)  评论(0编辑  收藏  举报