美文网首页
第1项 考虑静态工厂方法而不是构造函数

第1项 考虑静态工厂方法而不是构造函数

作者: Cocoonshu粽子 | 来源:发表于2018-07-11 14:10 被阅读24次

    类允许客户端获取实例的传统方式是提供公共构造函数。 还有另一种技术应该是每个程序员工具包的一部分。 类可以提供公共静态工厂方法,它只是一个返回类实例的静态方法。 这是布尔值的简单示例(布尔值的盒装基本类)。 此方法将布尔基元值转换为布尔对象引用:

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

    请注意,静态工厂方法与“设计模式”中的“工厂方法”模式不同。 此项中描述的静态工厂方法在设计模式中没有直接等效项。
    类可以为其客户端提供静态工厂方法,而不是公共构造函数,或者除了公共构造函数之外。 提供静态工厂方法而不是公共构造函数具有优点和缺点。

    静态工厂方法的一个优点是,与构造函数不同,它们具有名称。 如果构造函数的参数本身没有描述返回的对象,则具有良好选择名称的静态工厂更易于使用,并且生成的客户端代码更易于阅读。 例如,构造函数BigInteger(int,int,Random)返回一个可能为prime的BigInteger,它可以更好地表示为名为BigInteger.probablePrime的静态工厂方法。 (此方法是在Java 4中添加的。)

    一个类只能有一个具有给定签名的构造函数。 众所周知,程序员通过提供两个构造函数来解决这个限制,这两个构造函数的参数列表仅按参数类型的顺序不同。 这是一个非常糟糕的主意。 这种API的用户将永远无法记住哪个构造函数是哪个,并且最终会错误地调用错误的构造函数。 阅读使用这些构造函数的代码的人不会在不参考类文档的情况下知道代码的作用。

    因为它们具有名称,所以静态工厂方法不具有前一段中讨论的限制。 如果某个类似乎需要具有相同签名的多个构造函数,请使用静态工厂方法替换构造函数,并使用精心选择的名称来突出显示它们之间的差异。

    静态工厂方法的第二个优点是,与构造函数不同,它们不需要在每次调用时创建新对象。 这允许不可变类(第17项)使用预构建的实例,或者在构造时缓存实例,并重复分配它们以避免创建不必要的重复对象。

    Boolean.valueOf(boolean)方法说明了这种技术:它永远不会创建一个对象。 这种技术类似于Flyweightpattern [Gamma95]。 如果经常请求等效对象,它可以极大地提高性能,特别是如果它们创建起来很昂贵。

    静态工厂方法从重复调用返回同一对象的能力允许类在任何时候保持对存在的实例的严格控制。 执行此操作的类被称为实例控制。 编写实例控制类有几个原因。 实例控制允许类保证它是单例(第3项)或不可实例化(第4项)。 此外,它允许不可变值类(第17项)保证不存在两个相等的实例:a.equals(b)当且仅当a == b时。 这是Flyweight模式[Gamma95]的基础。 枚举类型(第34项)提供此保证。

    静态工厂方法的第三个优点是,与构造函数不同,它们可以返回其返回类型的任何子类型的对象。 这为您选择返回对象的类提供了极大的灵活性。

    这种灵活性的一个应用是API可以在不公开其类的情况下返回对象。 以这种方式隐藏实现类会导致API非常紧凑。 这种技术适用于基于接口的框架(第20项),其中接口为静态工厂方法提供自然返回类型。 在Java 8之前,接口不能有静态方法。 按照惯例,名为Type的接口的静态工厂方法放在名为Types的不可实例化的伴随类(Item 4)中。 例如,Java Collections Framework的接口有45个实用程序实现,提供不可修改的集合,同步集合等。 几乎所有这些实现都是通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出的。 返回对象的类都是非公共的。

    Collections Framework API比它导出45个单独的公共类要小得多,每个方便实现一个。 它不仅仅是减少了API的大部分,而是概念上的重量:程序员为了使用API而必须掌握的概念的数量和难度。 程序员知道返回的对象具有其接口指定的API,因此不需要为实现类读取其他类文档。 此外,使用这种静态工厂方法需要客户端通过接口而不是实现类来引用返回的对象,这通常是一种很好的做法(第64项)。

    从Java 8开始,消除了接口不能包含静态方法的限制,因此通常没有理由为接口提供不可实例化的伴随类。 许多公共静态成员应该放在接口本身中。 但请注意,可能仍有必要将大量实现代码放在这些静态方法后面的单独的包私有类中。 这是因为Java 8要求接口的所有静态成员都是公共的。 Java 9允许私有静态方法,但静态字段和静态成员类仍然需要公开

    静态工厂的第四个优点是返回对象的类可以随着调用而变化,作为输入参数的函数。 声明的返回类型的任何子类型都是允许的。 返回对象的类也可能因发行版而异。

    EnumSet类(Item 36)没有公共构造函数,只有静态工厂。 在OpenJDK实现中,它们返回两个子类之一的实例,具体取决于底层枚举类型的大小:如果它具有64个或更少的元素,就像大多数枚举类型那样,静态工厂返回一个RegularEnumSet实例, 有一个长的支持; 如果枚举类型有六十五个或更多元素,工厂将返回一个JumboEnumSet实例,由一个长数组支持。

    这两个实现类的存在对于客户端是不可见的。 如果RegularEnumSet不再为小枚举类型提供性能优势,则可以从未来的版本中消除它,而不会产生任何不良影响。 同样,如果证明有利于性能,未来版本可以添加EnumSet的第三或第四个实现。 客户既不知道也不关心他们从工厂回来的物品的类别; 他们只关心它是EnumSet的一些子类。

    静态工厂的第五个优点是,当写入包含该方法的类时,返回对象的类不需要存在。 这种灵活的静态工厂方法构成了服务提供者框架的基础,如Java数据库连接API(JDBC)。 服务提供者框架是提供者实现服务的系统,系统使实现可用于客户端,将客户端与实现分离。

    服务提供者框架中有三个基本组件:服务接口,代表一个实现; 提供者注册API,提供者用于注册实现; 以及服务访问API,客户端使用它来获取服务的实例。 服务访问API可以允许客户端指定用于选择实现的标准。 如果没有这样的标准,API将返回默认实现的实例,或允许客户端循环遍历所有可用的实现。 服务访问API是灵活的静态工厂,它构成了服务提供者框架的基础。

    服务提供者框架的可选第四个组件是服务提供者接口,它描述了生成服务接口实例的工厂对象。 在缺少服务提供者接口的情况下,必须反复实例化实现(第65项)。 在JDBC的情况下,Connection扮演服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。

    服务提供者框架模式有许多变体。 例如,服务访问API可以向客户端返回比提供者提供的服务接口更丰富的服务接口。 这是桥模式[Gamma95]。 依赖注入框架(第5项)可视为强大的服务提供者。 从Java 6开始,该平台包含一个通用服务提供程序框架java.util.ServiceLoader,因此您不需要(通常不应该)自己编写(第59项)。 JDBC不使用ServiceLoader,因为前者早于后者。

    仅提供静态工厂方法的主要限制是没有公共或受保护构造函数的类不能被子类化。

    例如,不可能在Collections Framework中继承任何便捷实现类。 可以说这可能是一种伪装的祝福,因为它鼓励程序员使用组合而不是继承(第18项),并且是不可变类型(第17项)所必需的。

    静态工厂方法的第二个缺点是程序员很难找到它们。

    它们不像构造函数那样在API文档中脱颖而出,因此很难弄清楚如何实例化提供静态工厂方法而不是构造函数的类。 Javadoc工具有一天可能会引起对静态工厂方法的注意。 在此期间,您可以通过引起对类或接口文档中的静态工厂的注意并遵守常见的命名约定来减少此问题。 以下是静态工厂方法的一些常用名称。 这份清单远非详尽无遗:
    from-一种类型转换方法,它接受单个参数并返回此类型的相应实例,例如:

    Date d = Date.from(instant); 
    

    •of - 一种采用多个参数的聚合方法 ,返回包含它们的此类型的实例,例如:

    Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING); 
    

    valueOf-一个更详细的替代来自和,例如:

    BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
    

    instance or getInstance-返回由其参数(如果有)描述的实例,但不能说具有相同的值,例如:

    StackWalker luke = StackWalker.getInstance(options);
    

    create or 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);
    

    总之,静态工厂方法和公共构造函数都有它们的用途,理解它们的相对优点是值得的。 通常静态工厂是优选的,因此在没有首先考虑静态工厂的情况下避免反射来提供公共构造器。

    相关文章

      网友评论

          本文标题:第1项 考虑静态工厂方法而不是构造函数

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