欢迎光临
感受代码之美

获取类实例,选择使用静态工厂方法还是构造函数?

获取客户端实例的传统方法是由类提供一个公共构造函数。还有一种在通常情况下更好的获取类实例的方式:由类提供一个公共静态方法。

请注意: 静态工厂方法与来自设计模式的工厂方法模式不同,静态工厂方法在设计模式中没有直接等价的方法。

使用静态工厂方法而不是公共构造函数的方式既有优点也有缺点。

一、优点

优点1: 静态工厂方法有确切名称。如果构造函数的参数本身并不能描述返回的对象,那么具有确切名称的静态工厂则更容易使用,生成的客户端代码也更容易阅读。

一个类只能有一个具有给定签名的构造函数。众所周知,程序员可以通过提供多个构造函数来绕过这个限制,这些构造函数的参数列表仅在参数类型、个数或顺序上有所不同。这真是个坏主意。面对这样一个 API,用户将永远无法记住该用哪个构造函数,并且最终会错误地调用不适合的构造函数。如果不参考类文档,阅读使用这些构造函数代码的人就不会知道代码的作用。

优点2: 静态工厂方法不需要在每次调用时创建新对象。

这允许不可变类使用预先构造的实例,或在构造实例时缓存实例,并重复分配它们以避免创建不必要的重复对象。如果经常请求相同的对象,特别是在创建对象的代价很高时,它可以极大地提高性能。

优点3: 静态工厂方法可以获取返回类型的任何子类的对象。

这种灵活性的一个应用是API可以在不公开其类的情况下返回对象。以这种方式隐藏实现类会形成一个非常紧凑的API。这种技术适用于基于接口的框架,其中接口为静态工厂方法提供了自然的返回类型。

在Java 8之前,接口不能有静态方法。接口的静态工厂方法按照惯例都是放在一个不可实例化(构造函数为私有函数)的伴随类中。例如,java.util.Collections这个类中,就提供了不可修改的集合、同步集合等一系列静态工厂方法。Collections框架API通过静态工厂方法,不仅仅是减少API 的数量,还有概念上的减少。减少程序员了使用 API 必须掌握的概念的数量和难度,不需要为实现类阅读额外的类文档。

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

优点4: 静态工厂方法在被调用时,可以根据入参的不同,返回声明的返回类型的任何子类型。

EnumSet类没有公共构造函数,只有静态工厂。在 OpenJDK 实现中,它们返回两个子类中的一个实例,这取决于底层enum类型的大小:如果它有64 个或更少的元素,就像大多数enum类型一样,静态工厂返回一个long类型的RegularEnumSet实例;如果enum类型有65个或更多的元素,工厂将返回一个由 long[] 类型的 JumboEnumSet 实例。

客户端看不到这两个实现类的存在。如果RegularEnumSet不再为小型enum类型提供性能优势,它可能会在未来的版本中被消除,而不会产生不良影响。类似地,如果事实证明EnumSet有益于性能,未来的版本可以添加第三或第四个EnumSet实现。客户端既不知道也不关心从工厂返回的对象的类;它们只关心它是EnumSet 的某个子类。

优点5: 静态工厂的第五个优点是,当编写包含方法的类时,返回对象的类不需要存在。

二、缺点

缺点1: 没有公共(public)或受保护(protected)构造函数的类不能被子类化。

缺点2: 程序员很难找到它们。

它们在API文档中不像构造函数那样引人注目,因此很难弄清楚如何实例化一个只提供静态工厂方法而没有构造函数的类。Javadoc 工具总有一天会关注到静态工厂方法。与此同时,你可以通过在类或接口文档中对静态工厂方法多加留意,以及遵守通用命名约定的方式来减少这个困扰。

Refference

  1. Effective Java – Chapter 2. Creating and Destroying Objects(创建和销毁对象)
赞(1)
未经允许禁止转载:四个空格 » 获取类实例,选择使用静态工厂方法还是构造函数?

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址