デザインパターンをkotlinで書いてみた Singleton編


オブジェクト指向で大切になるInterfaceの考え方やオブジェクトの再利用性を学ぶために「Java言語で学ぶデザインパターン入門」について学び、Javaとついでにkotlinで書いてみることにしました。
今回はSingletonについてまとめます。

※また、コメント欄にレビューをいただきました @sdkei さんありがとうございます。
レビューを元に修正しました内容を反映させましたので、その点も踏まえて書いていこうと思います。

Singletonとは

singletonとは要素を一個しか持たない集合を指す言葉で、下記役割を果たすパターンとして定義されています。

  • 指定したクラスのインスタンスが絶対に一個しかないことを保証したい
  • インスタンスが一個しか存在しないことをプログラム上で表現したい

Singletonクラス

このクラスではstaticフィールドとして定義されたsingletonメンバにSingletonクラスのインスタンスで初期化していますが、クラスのロード時に一度だけ初期化が行われます。
また、privateで外部からのアクセスを防ぐことでインスタンスが一個しか生成されないことを保証しているとの事。

kotlinの場合、staticという定義はなく、代わりにcompanion objectを定義します。

参考:Kotlin で static なメンバーをどう書くべきなのか?

また、companion objectを定義してデコンパイルするとCompanionオブジェクトを介したgetter/setterからアクセスすることになるのでconstか@JvmFieldを使用するとのこと。
しかし、constはプリミティブ型かString型のみなので非プリミティブ型は@JvmFieldを指定し、プリミティブ型を@JvmField指定すると2回初期値を代入するため使い分ける必要があるとのこと。
ただ、@JvmFieldはFieldに可視性を持たすために使用するため、今回のprivateでは使用できなかった。

参考:
【Kotlin】【Java】Kotlin Javaの比較メモ
Calling Kotlin from Java

追記:@sdkei さんレビューありがとうございます!
レビューをいただき、上記のような実装でなくてもKotlinにはオブジェクト宣言を使用してより容易に実装できるようになっているとのことです。
Singleton2.kt,SingletonSample2.kt

参考:
Kotlinでデザインパターン Singleton編
オブジェクト式と宣言

Singleton.java
class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {
        System.out.println("インスタンスを生成しました。");
    }
    public static Singleton getInstance() {
        return singleton;
    }
}
Singleton.kt
class Singleton {
    init { println("インスタンスを生成しました。") }
    companion object {
       private val singleton = Singleton()
       fun getInstance() = this.singleton
    }
}
Singleton2.kt
object Singleton {
    init { println("インスタンスを生成しました。") }
}

Mainクラス

SingletonSample.java
public class SingletonSample {
    public static void main(String[] args) {
        System.out.println("Start.");
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if (s1 == s2) {
            System.out.println("s1とs2は同じインスタンスです。");
        } else {
            System.out.println("s1とs2は同じインスタンスではありません。");
        }
        System.out.println("End.");
    }
}
SingletonSample.kt
fun main(args: Array<String>) {
    println("Start.")
    val s1 = Singleton.getInstance()
    val s2 = Singleton.getInstance()
    if (s1 == s2) println("s1とs2は同じインスタンスです。")
    else println("s1とs2は同じインスタンスではありません。")
    println("End.")
}
SingletonSample2.kt
fun main(args: Array<String>) {
    println("Start.")
    val s1 = Singleton
    val s2 = Singleton
    if (s1 == s2) println("s1とs2は同じインスタンスです。")
    else println("s1とs2は同じインスタンスではありません。")
    println("End.")
}
実行結果
Start.
インスタンスを生成しました。
s1とs2は同じインスタンスです。
End.

クラス図

所感

  • staticなフィールドとして定義されたsingletonメンバにSingletonクラスのロード時に一度だけ初期化を行い、privateで外部からのアクセスを防ぐことでインスタンスが一個しか生成されないことを保証していることを学んだ。
  • また、上記保証により、シリアルナンバーなどの一意である必要がある実装の際にメリットになることを学んだ。

  • Kotlinについては下記の点を学ぶことができた

  1. staticの代わりにcompanion object {}を定義する
  2. companion内でプリミティブ型とString型はconst, 非プリミティブ型は@JvmFieldを指定
  3. privateの場合はそのまま使用
  4. オブジェクト宣言により容易にSingletonパターンを使用できること

参考

下記を参考にさせて頂き、大変読みやすく、理解しやすかったです。

Kotlin で static なメンバーをどう書くべきなのか?
【Kotlin】【Java】Kotlin Javaの比較メモ
Calling Kotlin from Java
Kotlinでデザインパターン Singleton編
オブジェクト式と宣言