美文网首页
ITEM 1: 用静态工厂方法替代构造函数

ITEM 1: 用静态工厂方法替代构造函数

作者: rabbittttt | 来源:发表于2019-03-26 17:45 被阅读0次

    ITEM 1: CONSIDER STATIC FACTORY METHODS INSTEAD OF CONSTRUCTORS

    获取一个实例的传统方式是调用它的构造函数,然而还有另一种应当在每个程序员的工具箱里的方法 —— 类提供静态工厂方法。举一个简单的例子:

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

    注意,本节描述的静态工厂方法与工厂方法设计模式不同。

    一个类提供静态工厂方法替代构造函数的优势在于:

    (1)静态工厂方法有自己的函数名。
    它能够很好的描述方法返回的实例的具体语义。例如:BigInteger(int, int, Random),这个构造函数返回一个可能是质数的BigInteger实例,但你不可能在构造函数的方法名中体现出这一点。 BigInteger.probablePrime(int, int, Random) 这个名字就好多了。
    此外,一个类只能有一个签名相同的构造函数,如果两个构造函数有相同类型的入参,那程序员只能调整其中一个的入参顺序好让编译器通过,然而这样的构造函数对于用户来说就是一场遭难(没有文档的帮助你永远不会记得顺序)。而对于静态工厂方法来说,给它们一个合适的名字就解决了。

    (2)静态工厂方法被调用时,不要求每次新建一个实例。
    这就允许不变类使用之前构造的/缓存的实例返回,以避免重复构造不必要的实例。上文的Boolean.valueOf() 正是这样(这有点像享元模式),这样做能够提高性能,在需要频繁创建实例而它们的开销又较大时。
    这种做法还能使类对他们的实例进行控制,这被称作 instance-controlled (实例控制?)。类能利用这个技术实现单例、不可实例化。同时,它还能使不可变类保证不会有两个相同的实例存在。这个技术也是享元模式的基础,Enum types (Item 34) provide this guarantee.(?)

    (3)静态工厂方法可以返回一个子类实例。
    这给予了程序员很大的自由,API可以返回实例而不需要暴露实际的类。这种技术适用于基于接口的框架,其中接口为静态工厂方法提供了自然的返回类型。在Java 8之前,接口不能有静态方法。按照惯例,一个名为Type的接口的静态工厂方法被放在一个名为Types的非实例化伙伴类中。
    例如,Java Collections框架有45个接口的实用程序实现,提供不可修改的集合、同步的集合等等。几乎所有这些实现都是通过一个不可实例化类(java.util.Collections)中的静态工厂方法导出的。返回对象的类都是非公共的。
    集合框架API比导出45个单独的公共类要小得多,每个公共类对应一个方便的实现。减少的不仅仅是API的大部分,还有概念的权重:程序员必须掌握的概念的数量和难度,才能使用API。程序员知道返回的对象精确地拥有由其接口指定的API,因此不需要为实现类读取额外的类文档。此外,使用这种静态工厂方法要求客户机通过接口而不是实现类引用返回的对象,这通常是一种很好的实践(第64项)。从Java 8开始,接口不能包含静态方法的限制被消除了,因此通常没有理由为接口提供非实例化的伴随类。许多公共静态成员应该放在接口本身中,而不是放在类中。但是,请注意,仍然有必要将这些静态方法后面的大部分实现代码放在单独的包私有类中。这是因为Java 8要求接口的所有静态成员都是公共的。Java 9允许私有静态方法,但是静态字段和静态成员类仍然需要是公共的。

    (4)静态工厂方法返回的实例,可以随调用入参的不同而返回不同的子类实例。
    例如:EnumSet 类在 OpenJDK 实现中,可能返回两种子类实例,如果有64或更少的元素,就会返回 RegularEnumSet 实例 由一个单独的 long 实现;而如果有65个以上元素,就会返回 JumboEnumSet,由一个 long 数组实现。客户端既不知道也不关心从工厂返回的对象的类,它们只关心它是某个 EnumSet 的子类。如果有一天人们研究出性能比 RegularEnumSet 更好的方法,那么可以在未来的版本直接替换它。

    (5)在编写包含方法的类时,返回对象的类不需要存在
    这种灵活的静态工厂方法构成了服务提供者框架的基础,比如Java数据库连接API (JDBC)。服务提供者框架是一个系统,在这个系统中,提供者实现了一个服务,并且系统使这些实现对客户端可用,从而将客户端与实现解耦。
    服务提供者框架中有三个基本组件:表示实现的服务接口;提供程序注册API,提供程序使用该API注册实现;和服务访问API,客户端使用该API获取服务实例。服务访问API允许客户端指定选择实现的标准。如果没有这些标准,API将返回默认实现的实例,或者允许客户端遍历所有可用的实现。服务访问API是构成服务提供者框架基础的灵活静态工厂。
    服务提供者框架的第四个可选组件是服务提供者接口,它描述了生成服务接口实例的工厂对象。在缺少服务提供者接口的情况下,必须反射地实例化实现(第65项)。在JDBC中,Connection扮演服务接口的角色。DriverManager.registerDriver是提供者注册API。DriverManager.getConnection是服务访问API, Driveris是服务提供者接口。
    服务提供者框架模式有许多变体。例如,服务访问API可以向客户机返回比提供者提供的服务接口更丰富的服务接口。这是桥型[Gamma95]。依赖注入框架(第5项)可以被视为功能强大的服务提供者。自Java 6以来,该平台包括一个通用服务提供者框架Java.util.ServiceLoader,所以不需要,通常也不应该编写自己的(第59项)。JDBC不使用ServiceLoader,因为前者早于后者。

    静态工厂方法也存在缺点:

    (1)如果一个类没有public/protected 构造函数,那么就不能被继承
    例如,你不能继承任何 Collections 中的类,不过这也许是因祸得福,Java鼓励程序员对集合使用组合而不是继承。并且这也符合一个不可变类的要求。

    (2)静态工厂方法很难被程序员发现
    它们在API文档中不像构造函数那样“亮眼”,因此很难找出如何实例化一个提供静态工厂方法而不是构造函数的类。程序员可以通过在类或接口文档中关注静态工厂,并遵循公共命名约定来减少这个问题。下面是静态工厂方法的一些常见名称:

    • from, 一种类型转换方法,它接受一个参数并返回该类型的对应实例
      Date d = Date.from(instant)

    • of, 一种聚合方法,它接受多个参数并返回包含这些参数的这种类型的实例
      Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING)

    • valueOf, 相比from/of,一个更冗长的替代方案
      BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

    • instance / getInstance, 返回一个实例,该实例由它的参数(如果有的话)描述,但不能说具有相同的值
      StackWalker luke = StackWalker.getInstance(options);

    • create / newInstance 类似instance或getInstance,但方法保证每次调用返回一个新实例
      Object newArray = Array.newInstance(classObject, arrayLen);

    • getType, 与getInstance类似,但如果工厂方法位于不同的类中,则使用该方法。Type是工厂方法返回的对象类型
      FileStore fs = Files.getFileStore(path);

    • newType, 与newInstance类似,但如果工厂方法位于不同的类中,则使用该方法。Type是工厂方法返回的对象类型
      BufferedReader br = Files.newBufferedReader(path);

    • type, 一个简洁的getType和newType的替代方案
      List<Complaint> litany = Collections.list(legacyLitany);

      总之,静态工厂方法和公共构造函数都有各自的用途,了解它们的相对优点是有好处的。通常静态工厂是更好的选择,因此避免在没有首先考虑静态工厂的情况下提供公共构造函数。

    相关文章

      网友评论

          本文标题:ITEM 1: 用静态工厂方法替代构造函数

          本文链接:https://www.haomeiwen.com/subject/ricnvqtx.html