effective java 第三版 条目1:思考使用静态工厂方法而不是构造器

       在客户端,比较传统的去获取一个类的实例的方式是提供一个公共的构造器。但是这里有另一个应该成为每一个程序员工具包的的一项技术——一个类可以提供一个公共的返回一个类的实例的静态工厂方法!这里有一个简单的产生一个Boolean(boolean的包装类)的例子,这个方法将一个原始的boolean转化为一个Boolean对象引用:

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

      记住一点,静态工厂方法和设计模式中的工厂模式不相同。本条目描述的静态工厂方法不等于设计模式中的工厂模式。

      一个类能够使用静态工厂提供一个实例给他的客户端,而不是使用一个公共构造器。使用静态工厂方法既有优点也有缺点。

      提供静态工厂方法的第一个好处是,不像构造器,静态工厂方法有他们的名字。如果一个构造器给定的参数不能描述一个它将会返回的对象的具体含义,静态工厂方法将是一个好的选择!我们可以很简单地使用一个方法名来说明这个静态工厂方法将会返回一个怎样的对象实例,因而客户端的代码将会很容易去阅读。

      例如,一个将会返回一个  可能最佳的(probably prime)  BigInteger实例的构造器BigInteger(int,int,Random),使用名为BigInteger.probablePrime 的静态工厂方法将会更好地表达它的含义。(这个方法在java4被添加)

      由于java的构造器重载机制,一个类的构造器只能有被给定一个相同的签名。程序员们已经大约都知道使用不同顺序的参数类型来摆脱这个限制(比如(int,double)和(double,int))。但真的是一个非常糟糕的主意。这样一个API的使用者将永远不会记住哪个构造器的含义,最后就会导致调用了一个失误地错误的构造器。如果没有文档,阅读使用这种构造器的人将不会知道这个代码到底是什么意思!

     静态工厂方法的第二个好处是,不像构造器,他们不需要每一次去创建一个新的对象,它允许不可变的类(条目17)去使用重构的实例,或者去缓存一个已经构造过的实例,重复地分配这些已经创建过的对象来避免不必要的复制对象。这个Boolean.valueOf(boolean) 方法就是就是这个技术。它从来不创建对象,这个技术与享元模式相似。如果一个等价的对象经常被请求,这种技术表现非常棒。尤其是在这个对象创建代价非常高的时候!

     在实例存在的每个时刻,重复调用静态工厂方法返回相同对象的的能力将保持精准对的控制。这样做的类被称为instance-controlled。这里有许多原因去写一个可控实例,实例控制允许一个类保证它是单例的(条目3),或者不可实例化(条目4)。同时,它允许一个值不可变的类(条目17)保证没有第二个与他等价的实例存在:a.equals(b) if and only if a == b。这是这是享元模式的基础。枚举类型(条目34)就提供这样一个保证。

      静态工厂方法的第三个好处是,不像构造器,它可以返回一个任意返回类型的子类类型的对象。这给你更大的灵活性在选择返回对象的类型上。

     一个这种灵活性的应用是,一个API能够返回一个非public修饰的类的对象。隐藏实现的类将形成一个非常紧凑的API,这项技术把他借给 接口基础框架(interface-based frameworks)(条目20),这些接口使用静态工厂方法提供一个合理的返回类型。

在java8之前,接口不能有静态方法,通常而言,对于一个名为Type的t接口将会放入一个名为Types的不可实例化的组合类(条目4)。例如,java 集合框架中接口4/5功能实现,都会提供一个 不可修改的的包装集合类(unmodifiable ),加锁的包装集合类(synchronized),差不多的,所有的实现都是在一个不可实例化的类(java.util.Collections)通过静态工厂方法导出的,这些返回的对象的class都是非public的!

     集合框架的API比被他导出的4/5的分开的public classes要更小...(The Collections Framework API is much smaller than it would have been had it exported forty-five separate public classes, one for each convenience implemen- tation.???)。它不但是减少了API上的体积,而且也降低了概念上的体积:这个概念的数量和难度将会让程序员可以更好地去使用这个API。程序员知道返回的对象的功能已经精确地被接口指定,所以这里不需要去为了这些实现类去阅读额外的关于类的文档。除此之外,使用这样一个静态工厂方法需要客户端器关心这个由接口返回的对象而不是实现类,这通常是一个好的练习。(条目64)

  自从java8,接口中不能包含静态方法的限制被排除了,因此这里有一个典型的原因是为去接口提供一个不可实例化的同伴类。许多本来在一个类中的公共静态成员而不是被放在接口中。记住,无论如何,讲一个大体量的实现代码放在一个私有的静态方法中仍然很有必要!这是因为在java8中需要一个接口的所有静态方法必须是public的,java9允许私有静态方法,单是静态字段和静态成员类仍然必须是public的!

  静态工厂方法的第四个好处是,这个返回对象的class属性可以是变化的从调用到作为一个输入参数的函数被调用。任何声明类型的子类都是被允许的。这个返回对象的类从依赖到依赖同样可以是变化的。(A fourth advantage of static factories is that the class of the returned object can vary from call to call as a function of the input parameters. Any sub- type of the declared return type is permissible. The class of the returned object can

also vary from release to release.???)

  EnumSet类(条目36)没有公共的构造器,只有静态工厂。在OpenJdk实现中,他们返回了两个子类的其中一个类型的实例,返回的实例依赖于这个潜在的枚举类型的长度:如果有64或者更小的元素在枚举值,静态工厂方法返回一个RegularEnumSet实例,如果返回枚举类型有大于65的元素,这个工厂将会返回一个由大数组支持的JumboEnumSet 实例。

    对于客户端,这两个实现类的存在是不可见的。如果RegularEnumSet 导致提供一个更好的表现对于更小的枚举类型,它你能够从未来没有不良影响的释放中排除。相同的,一个未来的依赖能够添加1/3或者1/4EnumSet的实现如果它提供更好的 表现。客户端不用知道也不用关新他们从工厂中得到的类的class属性;他们可以只关心这些EnumSet是相同的。

     静态工厂方法的第五个好处是,这个返回对象的class不需要存在当这个类包含的这些方法被重写(返回匿名类)。许多来自于service provider frameworks的基础的灵活的静态工厂方法。像JDBC API。一个服务提供框架是一个提供实现服务的系统,这个系统使这些实现在客户端上可用,将客户端从这些实现中解耦。

     这里有三个在服务提供框架中的组件,一个是服务接口,它提供一个实现,一个是提供者注册接口,使用者通常注册实现,还有一个是可能被为了选择实现时被客户端允许而指定的标准的可通行的API。如果这个标准不存在,这个API将返回的默认实现的实例,或者允许客户端循环通过所有可用的实例。这个服务service access API就是 形成服务提供框架的基础 的 灵活的 静态工厂

一个可选择的服务提供框架的第四个组件是服务提供接口,它描述了一个产生服务接口实例的工厂对象,在这个服务提供接口标准中,实现被深思熟虑地实例化(条目32)。在JDBC这个问题中,Connection 是服务接口的部分,DriverManager.registerDriver是提供注册的API, DriverManager.getConnection是服务通行API,而Driver 是服务提供接口。

    这里有许多服务提供框架模式的变体,例如,服务通行API可以返回一个更多的服务接口到客户端而不是一个由提供者提供的实例。这是桥接模式。依赖注入框架(条目5)可以被看做有力的服务提供者。自从java6开始,java平台加入了通用目的服务提供框架 java.util.ServiceLoader,所以你不需要通常也不应该去写你自己的服务提供框架。JDBC没有使用ServiceLoader(条目59),因为他比ServiceLoader更早地出现。

       仅提供一个静态工厂方法的最主要的限制是这个类没有公共和保护级别的构造器,所以他们不能被继承。例如,想要继承Collections(非Collection)框架中方便的实现时不可能的!单是这个隐藏也可能是一个好处,因为它鼓励程序员多使用组合而不是继承(条目18)。同时这是被要求需要是不可变类型!(条目17)

     而静态工厂方法的第二个缺点是很难去发现它们。这些方法不像构造器那样存在在API文档中(比如Collections),所以很难去指出怎样通过静态工厂方法而不是一个构造器实例化一个类。JavaDoc工具可能将会有一天注意到静态工厂方法,在那时,你可以减少去关注类或者接口文档中的静态工厂方法的一些问题。

    这里有一些通常见到的静态工厂方法的命名方式:

    ①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——像getnstance,但是通常如果这个工厂方法将返回不同的class,Type是工厂方法返回的对象的类型。例如:FileStore fs = Files.getFileStore(path)

   ⑦newType——像newInstance,但是通常如果静态工厂方法将返回不同的class,Type是工厂方法返回的对象的类型。例如:BufferedReader br = Files.newBufferedReader(path)

    ⑧一个getType和newType的替代选择,例如:List litany = Collections.list(legacyLitany)

总结,静态工厂方法和构造器都有他们的用处,这需要花时间去理解他们相关的特点,通常静态工厂方法是更好的,所以最好在提供一个构造器之后要思考一下是不是可以优先提供一个静态工厂方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容