1. 概要

このチュートリアルでは、Scalaのコンパニオンオブジェクト、それらが何であるか、ファクトリパターンとエクストラクタを実装するためにどのように使用できるかについて説明します。

2. クラスとオブジェクト

Taskクラスで作業していると仮定しましょう。

class Task(val description: String) {
  private var _status: String = "pending"

  def status() : String = _status
}

Taskは基本的なScalaクラスです。Taskのオブジェクトを作成する場合は、そのプライマリコンストラクターを呼び出します。

val task = new Task("do something")
assert(task.description == "do something")

次に、s tatusをパラメーターとして受け入れるクラスに別のコンストラクターを追加しましょう。

class Task(val description: String) {
  private var _status: String = "pending"

  def this(description: String, status: String) = {
    this(description)
    this._status = status
  }

  def status():String = _status
}

これにより、特定のステータスを持つタスクオブジェクトを作成できます:

val task = new Task("do something", "started")
assert(task.status == "started")

補助コンストラクターをさらに追加できますが、いくつかの問題があります。

  • オブジェクトを作成するたびにnewキーワードを使用しますが、これは細かい部分ですが、避けるとよいでしょう。
  • すべての補助コンストラクターは、コンストラクターの伸縮をもたらす最初の式として既存のコンストラクターを呼び出す必要があります。 オブジェクトを簡単に作成するための流暢なAPIを作成するためのファクトリメソッドを追加するのは素晴らしいことではないでしょうか。 ファクトリメソッドは、コンストラクタに比べて多くの利点を提供します

static ファクトリメソッドを使用すると、上記の欠点を克服できます。

しかし、Scalaは静的修飾子を提供しません。 Scalaでは、すべての値がオブジェクトであり、すべての操作がメソッドです。 静力学があると、そのルールが破られます。 代わりに、Scalaにはシングルトンオブジェクトがあります。

シングルトンオブジェクトを使用して以前の欠点に対処する方法を見てみましょう。

3. コンパニオンオブジェクト

シングルトンオブジェクトがクラスと同じ名前を共有する場合、そのクラスのコンパニオンオブジェクトと呼ばれます。 クラスオブジェクトとコンパニオンオブジェクトの両方が、同じソースファイル定義されている必要があります。

前に作成したTaskクラスのコンパニオンオブジェクトを定義しましょう。

class Task(val description: String) {
  private var _status: String = "pending"
  def status():String = _status
}

object Task {
  def apply(description: String): Task = new Task(description)
}

同じ名前のシングルトンオブジェクトを追加しました。 Taskシングルトンオブジェクトにはapplyメソッドがあります。 applyメソッドはScalaの特別なメソッドであることを思い出してください。 これは、メソッド名なしで呼び出すことができるインジェクターです。

apply メソッドをコンパニオンオブジェクトに追加すると、newキーワードを使用せずにオブジェクトを作成できます。

val task = Task("do something")
assert(task.description == "do something")

これは、applyメソッドの呼び出しに似ています。 引数リストがオブジェクトの後に置かれると、Scalaは引数リストに一致するapplyメソッドを探します。

次のことを確認しましょう。

val task = Task.apply("do something")
assert(task.description == "do something")

apply メソッドをオーバーロードして、さまざまなパラメーターを持つさまざまなファクトリメソッドを作成できます。

ステータスパラメーターを受け取る別のapplyメソッドを追加しましょう。

class Task(val description: String) {
  private var _status: String = "pending"

  def status():String = _status
}

object Task {
  def apply(description: String): Task = new Task(description)

  def apply(description: String, status: String): Task = {
    val task = new Task(description)
    task._status = status
    task
  }
}

apply メソッドをオーバーロードし、descriptionに加えてstatusパラメーターも取得します。

val task = Task("do something", "started")
assert(task.status == "started")

上記の例では、コンパニオンオブジェクトがプライベートとしてマークされている_statusフィールドにアクセスできたことに注意してください。 これは、コンパニオンオブジェクトの主な利点の1つであり、クラスとそのコンパニオンオブジェクトは、プライベートであっても、互いのメンバーにアクセスできます。

コンパニオンオブジェクトは、より高度な apply メソッドを実装して、クラス階層全体をカバーするオブジェクトを作成できます。 親クラスオブジェクトは、シナリオにより適した特定のサブタイプを作成することを選択できます。 ファクトリは、このロジックをすべて非表示にして、ユーザーに統一されたインターフェイスを提供できます。

これは、同じタイプのオブジェクトのみを返す補助コンストラクターに比べて大きな利点です。

4. 抽出器

apply メソッドと同様に、 unapply メソッドを、オブジェクトインスタンスを分解できるエクストラクターとして機能するコンパニオンオブジェクトに追加できます。

タスクの説明とステータスを返すエクストラクタを追加しましょう。

object Task {
  def unapply(task: Task): Tuple2[String, String] = (task.description, task.status())
}

val task = Task("do someting")
val (description, status) = Task.unapply(task)
assert(description == "do something")
assert(status == "pending")

unapply を使用すると、Taskオブジェクトから説明とステータスを抽出できることがわかります。 u napply メソッドは、何でも返すことができ、強力なパターンマッチング式を有効にします。

5. 結論

この記事では、コンパニオンオブジェクトと、それらを使用してファクトリビルダーメソッドを作成する方法について学習しました。

また、コンパニオンオブジェクトがクラスのプライベートメンバーにアクセスする方法と、その逆の方法についても説明しました。 unapplyメソッドを実装してエクストラクターを有効にする方法を見ました。

いつものように、すべてのコード例はGitHubにあります。