Java 的 idiom(1)

写了半年的 Java 了快,也是需要多方面看一些较为完整的东西,对应于前面看 C++ 的 idiom 系列,不过 Java 很多东西还是跟 C++ 共通的。这里从 Effective Java 开始。

static factory method vs constructor

嗯,几个优缺点分析的还是很到位的:

  • static factory method 是函数,有名字的,因此你可以为创建不同用途的同类型对象使用不同的名字,读起来比较容易懂
  • factory method 并不一定要创建新的对象,而构造函数则必然会创建新的对象,如果你的对象有特殊的要求,如 singleton 或者 prototype 类型,往往需要通过 factory method 管理这些或者选择其中一个返回
  • factory method 可以返回对应的子类实现,而构造函数只能返回本身这个类的对象,因此很多时候我们会为一些不能实例化的类 Type 构造一个 Types 类,其作用就是提供 factory method,用来根据某些情况产生这个 package 里面对应这个 Type 的具体实现,java.util 里面不少如 Collections、Arrays 等,这些类从某个角度来说类似于这个 package 的 facade,用户有时甚至可以不必理会其他的类,完全通过这些类提供的 factory 创建需要的对象,然后根据 Type 里面的接口就可以玩了
  • 减少创建 parameterized type 的累赘,其实这在 C++ 里面也是一样的,通过模板函数产生对象,而不是直接使用构造函数(new),这样一来可以通过 type inference 推断出产生的类型,不过 Java 没有 auto,所以还是很悲催的

缺点是:

  • 为了禁止显式调用构造函数,往往需要将构造函数弄成 private 的,这样一来继承就成了大的问题
  • 在文档中 factory method 和其他的 static method 混在一起不是很容易区分出来

常见的 factory method 的名字有 valueOf、of、getInstance、newInstance、getType、newType。

构造一个较多参数的对象可以使用 builder

为了方便别人构造一个参数较多的对象,一种方法就是通过提供多个 constructor,将某些情况下的默认值填到一个最完整的构造函数里面去,这一般称为 telescoping constructor。但这种解决方案并不能很好的 scale,同时由于都是一样的名字(构造函数),很容易混淆其中的细微区别。

另一种常在 IoC 里面见到就是 java bean 类型的,让这个类是 default constructible 的,之后通过一系列 setter 将对应的 value 插入到对象里面去,通常还有一个 init 方法在 setter 结束后用来初始化这个对象。在有 IoC 的时候(如 spring),这算是还不错,但是问题是这些 setter 反而让任何人都可以在运行时改变这个对象了(而这有时候是不希望的)。而在初始化这个 bean 的过程中,对象是不可用的。同时这个对象也不可能是 immutable 的了(尽管可以在使用中避免更改其状态)。

比较好的解决方案是 builder,往往 builder 实现使用 static inner class,这样它可以访问外围类的 private 类型的构造函数(这个构造函数也可以访问 inner class 的任意成员)。这个 builder 的实现往往有如下特点,default constructible,提供很多方法对应于每个(组)参数,这些方法都会返回这个 builder 自己的引用,提供一个结束方法,这个方法通过外围类的构造函数产生需要的对象。这样一来使用这个 builder 有点像使用有名字的参数(id=123, name=”foo”)的函数一样。

当然 Java 有很多类都有一些外部的 builder,如 String,这些类可能本身是 immutable 的,于是需要通过一个 builder 产生复杂的对象,避免反复产生新的对象。

Java 的 Class 提供了 newInstance 方法用来使用 default constructor 构造对象,但这非常容易导致很丑的代码(可能不存在对应的构造函数,你得处理对应的 exception),还会破坏 compile-time 的类型检查。一种做法是提供一个 generic 的 builder 接口,要求所有的 builder 实现,当然 JDK 没设计好,所以没这玩意了…

Singleton 的实现

其实 Singleton 说起来简单,要写好真的很难。一些简单的套路大家都知道,private 的构造函数,通过 private static final 的对象保存这个唯一的对象,通过一个 public static getInstance 方法提供对这个对象的访问。这样实现的一个问题是对象必须在 load 这个类的时候初始化,如果希望使用 lazy instantiation,我们就得去掉 final,而在 getInstance 的时候检查这个 static 变量是否为 null 来决定初始化对象。这个问题在多线程下面会变的更加复杂,一个偷懒的策略是为 getInstance 加上 synchronized 关键字,但是这样一来就会导致后续的 getInstance 调用都引入了额外的 synchronized 开销(有时候明明可以并行 getInstance 的),这时候需要使用 double checking 策略,例如

public class Foo {
  public static Foo getInstance () {
    if (instance == null) {
      synchronized {
        if (instance == null)
          instance = new Foo () ;
      }
    }
    return instance ;
  }

  private static volatile Foo instance ;
}

当然 singleton 还有 serialization 的问题,因为 deserialize 的时候我们需要避免产生新的 instance,java.io.Serializable 需要实现 writeObject、readObject 和 readObjectNoData 三个方法,这三个都是 private 的(好奇那什么调用这些方法呢?),singleton 往往需要关心 readResolve 和 writeReplace。还需要注意的是需要通过 transient 关键字标注所有的成员。

在 Java 5 之后可以用 enum 来实现 singleton,这时 enum 和一个类一样,可以有自己的方法,我们只需要提供唯一的一个 enum 值 INSTANCE 就行了。

——————
And they digged another well, and strove for that also: and he called the name of it Sitnah.

Advertisements
Java 的 idiom(1)

一个有关“Java 的 idiom(1)”的想法

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s