The King's Museum

ソフトウェアエンジニアのブログ。

【Effective Java 3rd】Item 1: Consider static factory methods instead of constructors

オブジェクトを生成するために public コンストラクタの他に static ファクトリーメソッドを用意するとよいでしょう。

static ファクトリーメソッドとは、インスタンスを返す単なる static メソッドのことです。 例えば、Java の Boolean では次のような static ファクトリーメソッドが提供されています。

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

なお、GoF のデザインパターンで議論される「ファクトリーメソッド」とは異なるので注意が必要です。

Advantages

通常のコンストラクタと比較して static ファクトリーメソッドには次のようなメリットがあります。

名前がつけられる

コンストラクタの名前は必ずクラス名と一致しなければなりません。

一方、static ファクトリーメソッドではより適切な名前をつけることができます。

インスタンスの生成をコントロールできる

コンストラクタは必ずインスタンスを生成してしまいます。

一方、static ファクトリーメソッドでは必ずしもインスタンスを生成する必要はありません。 これは GoF のデザインパターンの Flyweight パターンに相当します。 さきほどの Boolean.valueOf はまさにこのパターンです。

インスタンスの生成をコントロールできることで、シングルトン(項目3)、インスタンス化不可能(項目4)、不変クラス(項目15)などのパターンに応用することもできます。

サブタイプのインスタンスを生成できる

コンストラクタは必ずそのクラスのインスタンスを生成します。

一方、static ファクトリーメソッドでは必ずしもそのクラスのインスタンスを返す必要がありません。 例えば、実装のためだけの非公開クラスを公開することなく、そのインスタンスを生成することができます。

Java8 以前ではインタフェースが static メソッドを持てませんでした。 そのため、Type というインタフェースを継承した実装クラスを生成するため、インスタンス化不可能な Types という Utility クラスを定義して static ファクトリーメソッドを用意していました。 しかし、Java8 からはインタフェースがインタフェースが static メソッドを持てるようになったのでこのテクニックの必要性はうすれています。

引数の値によって生成するインスタンスを変更できる

コンストラクタは生成する引数によってインスタンスを変更できません。

一方、static ファクトリーメソッドを使うと入力値によって生成するインスタンスを変更可能です。

EnumSet がよい例です。 EnumSet のファクトリーメソッドは enum 型の値の個数によって、生成するインスタンスを変更しています。 具体的には enum 要素が 64 以下なら RegularEnumSet のインスタンスを返しますが、それより大きければ JumboEnumSet のインスタンスを返します。

クラス作成時には存在しないインスタンスを生成できる

当たり前の話ですが、あるクラスのコンストラクタを作成するとき、生成するインスタンスのクラスは必ず定義されています。

一方、static ファクトリーメソッドでは作成時点では、返すクラスの定義は存在していなくてもかまいません。 この柔軟なファクトリーメソッドは Service Provider Framework の基本となります。

Disadvantages

static ファクトリーメソッドにはいくつかのデメリットもあります。

public または protected のコンストラクタを持たないクラスをサブクラスにできない

この制限は不便に感じるかもしれませんが、実際には問題ありません。 なぜなら、そのような場合は継承ではなく、コンポジョンの利用を促すからです。

他の static メソッドと区別がつきづらい

static ファクトリーメソッドのための API ドキュメントが存在しません。そのため、ドキュメント上で通常の static メソッドと static ファクトリーメソッドの区別がつきません。

これに対しては一般的な命名規則に従うことで弱点を克服しています。以下に命名規則の例を示します。

  • from()
    • 一つのパラメータをもらって対応するインスタンスを返します。
  • of()
    • 複数のパラメータをもらってそれらを取り扱うインスタンスを返します。
  • valueOf()
    • from() や of() の代替です。
  • instance() or getInstance()
    • パラメータで指定されたインスタンスを返します。
    • 入力したパラメータと同じ値を持つとは限りません。
  • create() or newInstance()
    • getInstance に似ていますが、必ず新しいインスタンスを生成します。
  • getType()
    • getInstance に似ていますが、ファクトリーメソッドが対象のクラスと異なるクラスにある場合に利用されます。
    • Type は返されるオブジェクトの型の名前です。
  • newType()
    • newInstance に似ていますが、ファクトリーメソッドが対象のクラスと異なるクラスにある場合に利用されます。
    • Type は返されるオブジェクトの型の名前です。
  • type()
    • getType や newType の代替です。

Effective Java

Effective Java

(c) The King's Museum