2012年1月30日月曜日

Scalaでシングルトンパターン

Scalaでシングルトンパターンを書いてみました。
ベターJavaの呼び声高いScalaですが、やはりJavaと全く同じという訳にはいきませんでした。
ちょっと悩んだのでメモとして残します。
objectやstaticの扱いのサンプルにもなると思います。

そもそもScalaでstatic使えるの?
まず、Scalaにはstaticが存在しません。
その代わりに、シングルトンオブジェクトを生成するobjectというものがあります。

なにそれ?
シングルトンオブジェクトという名前が表す通り、シングルトンなオブジェクトです。
特徴として、new演算子を使わなくてもメソッドの実行や変数の参照が出来ます。
ClassName.getInstance() ←こんな感じです。

それってJavaのstaticで宣言されたメソッドとか変数呼び出すのと同じじゃん?
その通りです。呼び出しだけでなく、動作自体もJavaのstaticな変数やメソッドと同じです。
そもそもすでに述べたように、Scalaにはstaticが存在せず、その代わりにobjectという仕組みが用意されているのです。
Javaでstaticとして宣言される変数やメソッドを、Scalaではobjectの中に切り出してあげるのです。

なるほど。じゃあ具体的にどうやって宣言するの?
これはソースを見ていただければ一目瞭然です。
通常、クラスを宣言する際は
class className {...}
となりますが、objectの場合は、
object ObjectName {...}
となります。

ではサンプルソースをご覧ください。

class SingletonSample private( var list:List[Int] ) {

         // リストの先頭に値を追加
        def add( x:Int ) {
            list = x :: list
        }
}

object SingletonSample {

    private val sample:SingletonSample = new SingletonSample((1 to 10).toList)

    def getInstance():SingletonSample = {
         sample
    }

    def apply():SingletonSample = {
         getInstance
    }
}


REPLでの実行結果は以下の通りです。

#インスタンスの生成
scala> val sample = SingletonSample.getInstance
sample: SingletonSample = SingletonSample@151fe8a

#取得したインスタンスの中身(list)をチェック
scala> sample.list
res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

#取得したインスタンス内のリストの先頭に0を追加
scala> sample.add(0)

#0が先頭に追加されていることを確認
scala> sample.list
res3: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

#再度インスタンスを取得してみる
scala> val sample2 = SingletonSample.getInstance
sample2: SingletonSample = SingletonSample@151fe8a

#取得したインスタンスのリストの中身の先頭に0が追加されている
#このことから、シングルトンパターンとして正常に動作していることが分かる
scala> sample2.list
res2: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

ほぉ、簡単ジャン!
ただし気を付けなければならない点が一つあります。
Javaで「staticな変数やメソッド」と、「staticでない変数やメソッド」が入り混じっているクラスの場合、Scalaではstaticな部分をobjectの中に切り出さなければならないのは説明しました。
気を付けなければならない唯一の点とはまさにその切り出し方で、class名とobject名を同一のものにしなければならない「場合がある」という点です。
Scalaでは、class名とobject名が同じものをコンパニオンオブジェクトと呼びます。
この状態が、Javaの「staticな変数やメソッド」と、「staticでない変数やメソッド」が入り混じっているクラスと同じ状態になります。

class名とobject名を同じにしなければならない「場合」って?
サンプルソースを見ていただければ分かると思いますが、シングルトンパターンの場合、実際に生成されるクラスのコンストラクタはprivateにします。
これはJavaでも同じです。シングルトーンパターンなので当然です。
ただ、コンパニオンオブジェクトでない場合、privateでコンストラクタが宣言されているクラスのインスタンスを生成できないのです。
これは、Java的に説明すると、objectが別のクラス内のstaticで宣言されたメソッドや変数という扱いになるためです。
コンパニオンオブジェクトの場合であれば、Java的にはclassとobjectが実質同じクラス内と考えて良いので、インスタンスを生成することができます。

・・・よくわからなくなってきた
極論ではありますが、Javaのstatic混じりのクラスをScalaで書く場合に、staticな変数やメソッドは、クラス名と同じ名前で宣言されたobjectの中に書いてしまえばOKです。
staticなメンバが入っているJavaのクラスHogeがある場合、Scalaではstaticな部分のみ切り出して、Hogeという、クラスと同名のobjectの中に移動してあげるだけです。

ちなみに、コンパニオンオブジェクトを生成したい場合は、同名のclassとobjectを用意するのですが、同一ファイル内に記述しなければならないという制約もあります。