KotlinにおけるFactoryメソッドの実装パターン


内容3行まとめ

  • クラスのインスタンス生成には、コンストラクタよりFactoryメソッドのほうがベター
  • KotlinだとFactoryメソッドの書き方が色々ある
  • 大抵のケースで一番良いのは、Companion Object内にFactoryメソッドをそれっぽい名前(of,from等)で実装するやり方

参考書籍

最近、Effective Kotlinなるものがbeta releaseでpublishされていたため、その中の面白そうな内容をピックアップして自分なりにまとめ直してみました。
(関係ないですが、本をbeta releaseとして電子版で安く提供するって、今風のLeanなやり方で面白いですね)

Effectiveとついていることからわかるように、プログラミング言語ごとによく出版されているEffectiveシリーズです。
Java界隈だとEffective Javaが言わずとしれた名著になっていると思うのですが、Kotlin版が出たということで気になって読んでみました。
(ちなみにEffective Kotlinを書く上で著者は他のEffective XXXシリーズの内容を色々と参考にしながら作っているそうです)

この記事を書く上で参考にした章はItem33: Consider factory functions instead of constructorsです。

参考)javaにおけるコンストラクタ・Factoryメソッド

javaだと例えば以下のようにstaticメソッドでFactoryメソッドを表現できたりしたかと思います。

public class User {

    private final String name;
    private final String email;

    //コンストラクタ
    private User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    //Factoryメソッド
    public static User from(String name, String email) {
        return new User(name, email);
    }
}

(Effective Javaによると)Javaにおいてもインスタンス生成にはコンストラクタよりもFactoryメソッドのほうが推奨されていました。

推奨されている理由や、Kotlinではどう実装するのかについて、EffectiveKotlinではどう書かれているのかを以下で見てみましょう。

Factoryメソッドがコンストラクタより推奨される理由

まるで親の仇かのようにめっちゃ大量に(なんと8つ!)理由書かれてましたw
重要そうなのだけピックアップしました。

  1. コンストラクタと違って、関数(Factoryメソッド)には名前がつけられる
    ・ 生成方法ごとにわかりやすい名前がつけられる!
  2. コンストラクタと違って、関数(Factoryメソッド)は返り値をサブタイプにすることができる
    ・ 例えばlistOfメソッドの返り値はList型になっており、Listはinterfaceであるため、Listのサブタイプを返すことができる
  3. コンストラクタと違って、関数(Factoryメソッド)は必ずインスタンス生成する必要がない。
    ・ シングルトンオブジェクトを返したり、キャッシュされている値を返したりできる。
  4. 後述するTop-Level関数を使う場合などは、関数(Factoryメソッド)は可視性を柔軟に制御できる
    ・ 例えばprivateをつけてTop-Level関数を定義することで、そのファイル内でのみ利用できるFactoryメソッドを定義できる。
  5. 関数(Factoryメソッド)は、コンストラクタの引数とは異なる引数にすることができる
    ・ 上のjavaのUserクラスの例だと、Userクラスを引数に取る関数(Factoryメソッド)を作ったりすることもできる。

KotlinでのFactoryメソッド実装パターン

なんと5つも紹介されていました。

  1. Companion object factory function
  2. Extension factory function
  3. Top-level factory functions
  4. Fake constructors
  5. Methods on a factory class

1. Companion object factory function

こういうやつです。
Javaと書き方が似ていることや課題が少ないこともあり、最も推奨する方法とのことでした。

class User(val name: String) {
    companion object {
        fun from(name: String): User {
            return User(name)
        }
    }
}

ちなみにメソッド名としては以下のようなものが慣例的によく使われるためオススメのようです。

  • from: 返り値がその型そのものの場合(上の例だとUser)
  • of: 複数引数を受け取りそれらを集約するものを返す場合(上の例だと例えば fun of(vararg names: String): List<User> とか)
  • valueOf: fromやofと似たような使い方

2. Extension factory function

以下のようなやつ。companion objectが(例えばライブラリ内にあるなどして)修正が加えられないようなときに使えるパターン。
但し以下コードにあるように、例え空であったとしてもcompanion object定義(companion object {}の部分)が無いと使えないのが欠点。

interface Item {
    companion object {}
}

fun Item.Companion.create(): Item { TODO() }

3. Top-level factory functions

listOf関数のようにTopレベルで定義するやり方。
privateをつけ、そのFactoryメソッドをそのファイル内でだけしか使えないようにする、といった使い方に向いている。

ただTop-levelであるため、IDEの変換候補に出てきてしまうため、変なのを作ると同僚から知らぬ間に恨まれことになるので注意。

4. Fake constructors

List型においてこんな書き方できるの知ってましたか?(僕は知りませんでした)
めちゃコンストラクタっぽい見た目ですが、実態は関数。というわけでFake constructorと名付けられているようです。

List(4) { "User$it" } // [User0, User1, User2, User3]

以下のような感じの実装になっているようです。
実態は関数なのに命名規約がクラスと同じ(大文字スタート)というトリッキーなやり方なので、使うケースは少なそうです。

public inline fun <T> List(size: Int, init (index: Int) -> T) : List<T> = TODO()

5. Methods on a factory class

「UrlBuilderクラスがあり、それを経由してUrlクラスのインスタンスを生成する」といったケースです。
Builderパターンを使いたい場合は使うべきですが、通常は1のCompanion Object内に定義する方法を使うほうがシンプルです。

おまけ

4のFake constructorの実装方法は頑張れば以下のような方法でもできるようですw

class Tree<T> {
    companion object {
        operator fun <T> invoke(size: Int, generator: (Int) -> T): Tree<T> = TODO()
    }
}

//Usage
Tree(3) { "$it" }

関連資料

2019/11/27にKotlin愛好会のLTで同内容発表してきました。
(サンプルコードがこの記事よりもやや充実している部分もあります)
https://speakerdeck.com/doyaaaaaken/kotlinniyorufactorymesotudoshi-zhuang-patan

毛色が結構違いますが、他にもQiitaでKotlinでのFactoryメソッドの実装について書いている人がいるようです。
https://qiita.com/Takunawa/items/27e191fc37833b91edea
https://qiita.com/kaleidot725/items/78ad17691bfb3c33f87d