41
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

コンパニオンオブジェクトって何のためにいるの? for Rubyist

Last updated at Posted at 2016-02-25

初めてRubyからscalaに来たとき、「なんで特異クラスっぽい? シングルトンパターンっぽい?やつがいっぱいいるの?」と悩みました。なぜならrubyのクラス変数やシングルトンパターンはアンチパターン扱いされるほど嫌われていたからです。

しかし、 scalaのコンパニオンオブジェクトとrubyの特異クラスは見かけ上似ていますが、意味が違います。

この記事ではRubyエンジニア目線で、Scalaのコンパニオンオブジェクトについて解説していきたいと思います。

コンパニオンオブジェクトとは?

「コンパニオンオブジェクト」とは、あるクラスに対して同じスコープ、同じ名前で定義されたシングルトンオブジェクトです。

class Person
object Person

これがコンパニオンオブジェクトです。
が、シングルトンオブジェクトに対して漠然とした不安を感じます。。

コンパニオンオブジェクトはrubyのintializeみたいなもん

しかし、その不安はすぐに安心に変わります。
なぜなら、scalaのコンパニオンオブジェクトは、Rubyのinitializeみたいなものであり、大抵のクラスにおいて必要な機能だからです。

例えばRubyだとインスタンスを生成する際にこう書きます。

class Person
  def initialize(name)
    @id = 1000
    @name = name
  end
end


Person.new("マイケル")

しかし、scala(静的型付き言語)でRubyのノリで書こうとすると...

class Person(id: Int, val name: String)

object Main extends App {
  new Person("マイケル")
}
//-> コンパイルエラー

型の制約があるので、コンパイルエラーが出てしまいます。
rubyはinitializeだけで、外側からメンバーの値を与えてあげなくても、メンバを持たせることができます。
しかし、静的型付き言語ではクラスに型を定義する必要があるので(Rubyと違うところ)、 型を定義されたクラスと、クラスを生成するものが必要となります。それがコンパニオンオブジェクトです。

scalaではこう書きます。


class Person(id: Int, name: String)

object Person {
  def apply(name: String):Person = {
    val id = 1000
    new Person(id, name)
  }
}


object Main extends App {
  
  // Person.apply("マイケル")
  // apply関数は省略できる
  Person("マイケル")
}

この段階で、コンパニオンオブジェクトの必要性がわかりました。
しかし、このままだとプログラム上まだ問題があります。
それは、複数の方法でインスタンスを生成できてしまうことです。


object Main extends App {
  val person1 = Person("マイケル")
  val person2 = new Person(2000, "ボブ")
}

そこで、Personクラスの引数(コンストラクタ引数)をprivateにして、person2の記法を封印したいと思います。


// コンストラクタ引数をprivateにした
class Person private (id: Int, name: String)

object Person {
  def apply(name: String):Person = {
    val id = 1000
    new Person(id, name)
  }
}


object Main extends App {  
  val person1 = Person("マイケル")
  val person2 = new Person(2000, "ボブ")
}

// -> person2を生成しようとするとコンパイルエラー

Memberクラスのidとnameはprivateなので外からアクセスすることができません。
しかし、 コンパニオンオブジェクトのコンテキストではコンパニオンクラスのprivateにアクセスすることができます。

これがコンパニオンオブジェクトの特徴です。

classとコンパニオンオブジェクトに定義するべきものはなに??

簡単に言うと、Rubyでクラスに対して定義していたものは、コンパニオンオブジェクト、Rubyでインスタンスに対して定義していたものはクラスに定義します。

つまり、


class Person 
  DEFAULT_ID = 1000
  
  class << self
    def find_by_name(name)
      # do something
    end
  end
  
  def initialize(name)
    @id = DEFAULT_ID
    @name = name
  end
  
  def name_with_sama
    self.name + '様'
  end
end

をscalaで書くと...


class Person(id: Int, name: String){
  val name_with_sama:String = name + "様"
}

object Person {
  val default_id = 1000

  def apply(name):Person = {
    new Person(default_id, name)
  }
  
  def findByName(name):Person = {
    // do something
  }
}

まとめ

  • 外から渡したくないメンバをもったクラスを作るときに、コンパニオンオブジェクトを使う。
  • コンパニオンオブジェクトのコンテキストではコンパニオンクラスのprivateにアクセスすることができる。
  • クラスに対して定義するものはコンパニオンオブジェクト、インスタンスに対して定義するものはクラス。
41
35
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?