1. 序章

Scalaは関数型プログラミングとオブジェクト指向プログラミングのハイブリッドであり、オブジェクト指向言語と関数型言語の機能をスムーズに統合します。

このチュートリアルでは、クラスの定義、カプセル化、継承、ポリモーフィズムなど、オブジェクト指向プログラミングの概念をScalaに実装する方法を学習します。

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

2.1. クラスの定義

Scalaでは、classキーワードを使用してクラスを定義できます。

class Bread

クラスが定義されたら、newキーワードを使用してそのクラスのオブジェクトを作成できます。

val whiteBread = new Bread

2.2. フィールドの定義

クラスをより意味のあるものにするために、クラスにフィールドを追加できます。 変数を定義するように、varまたはvalを使用してクラスのフィールドを定義できます。

class Bread {
  val name: String = "white"
  var weight: Int = 1
}

そして、作成したオブジェクトから直接フィールドにアクセスできます。

val whiteBread = new Bread
assert(whiteBread.name === "white")
assert(whiteBread.weight === 1)

weight は変更可能なオブジェクトであるため、次のように変更できます。

whiteBread.weight = 2
assert(whiteBread.weight === 2)

2.3. コンストラクターの定義

コンストラクターは、クラスのフィールドまたはメソッドで後で使用されるクラスの値を割り当てるために使用されます。 コンストラクター定義でデフォルト値を割り当てることもできます。

class Bread(breadName: String = "white") {
  val name: String = breadName
}

ここで、コンストラクターに値を割り当てた場合に何が起こるかを見てみましょう。

val grainBread = new Bread("grain")
assert(grainBread.name === "grain")

2.4. メソッドの定義

def キーワードを使用して、クラスにメソッドを定義できます。 getPriceメソッドをBreadクラスに追加して、パンの重量と価格単位に基づいて価格を計算してみましょう。

class Bread(breadName: String = "white", breadWeight: Int = 1) {
  val name: String = breadName
  var weight: Int = breadWeight

  def getPrice(priceUnit: Int): Int = {
    priceUnit * weight
  }
}

戻り値はメソッドの最後の命令の結果であるため、returnキーワードを指定する必要はありません。 クラスのオブジェクトを作成した後、メソッドを呼び出すことができます。

val bread = new Bread("grain", 2)
assert(bread.getPrice(2) === 4)

3. カプセル化

カプセル化は、オブジェクト指向プログラミングの基本的な概念の1つです。 クラス内でデータとそのデータを処理するメソッドをバンドルするという考え方について説明しています。

サンドイッチを表すクラスを考えてみましょう。

import scala.collection.mutable.ArrayBuffer

class Sandwich(bread: Bread, filling: ArrayBuffer[String]) {
  private def getFillingsName: String = {
    filling.mkString(", ")
  }
}

クラス定義では、すべてのプロパティとメソッドはプライベートであり、作成したオブジェクトからそれらにアクセスすることはできません。 これを行うと、コンパイルエラーが発生します。

val sandwich = new Sandwich(new Bread("white"), ArrayBuffer("strawberry jam", "chocolate"))
sandwich.bread // error: value bread is not a member of Sandwich
sandwich.filling // error: value filling is not a member of Sandwich
sandwich.getFillingsName // error: method getFillingsName in class Sandwich cannot be accessed in Sandwich

パブリックメソッドを使用してそれらにアクセスまたは操作できます。

def getDescription: String = {
  s"This is a sandwich with ${bread.name} bread and $getFillingsName filling"
}

def addFilling(extraFilling: String): Unit = {
  filling.append(extraFilling)
}

定義されたメソッドを使用して、クラスのプライベートフィールドとメソッドにアクセスして操作できるようになりました。

val sandwich = new Sandwich(new Bread("sourdough"), ArrayBuffer("chicken"))
sandwich.addFilling("lettuce")
assert(sandwich.getDescription === "This is a sandwich with sourdough bread and chicken, lettuce filling")

4. 継承

継承は、別のクラスからクラスを派生できるようにするメカニズムです。これにより、派生クラスは、派生クラスからフィールドとメソッドを再利用できます。 フィールドとメソッドを再利用するクラスはサブクラスと呼ばれ、それらを提供するクラスはスーパークラスと呼ばれます。 サブクラスは、スーパークラスで定義されているすべての非プライベートフィールドとメソッドを継承します。

別のクラスに基づいてクラスを作成するには、extendsキーワードを使用します。 最初に継承される基本クラスVehicleを定義しましょう。

class Vehicle(val numWheels: Int, val color: String) {
  def accelerate(): Unit = { println("Vroom Vroom" }
}

次に、クラス Vehicleを継承する別のクラスBicycle、を定義できます。

class Bicycle(bikeColor: String, val bikeType: String) extends Vehicle(2, bikeColor) {
  def maxSpeed(): Int = {
    bikeType match {
      case "road" => 60
      case _ => 20
    }
  }
}

そこでは、BicycleVehicleのすべてのフィールドとメソッドに加えて、追加のbikeTypeフィールドとmaxSpeedメソッドを継承することが期待できます。 :

val bicycle = new Bicycle("red", "road")
bicycle.accelerate()
assert(bicycle.numWheels === 2)
assert(bicycle.color === "red")
assert(bicycle.bikeType === "road")
assert(bicycle.maxSpeed() === 60)

5. ポリモーフィズム

ポリモーフィズムは、入力のタイプに応じて異なる方法でデータを処理するOOP言語の機能です。 ここでは、2種類のポリモーフィズムについて説明します。 メソッドのオーバーロードとメソッドのオーバーライド。

5.1. メソッドのオーバーロード

メソッドのオーバーロードとは、メソッド名を使用することを意味しますが、メソッドで定義されたパラメーターに応じて、異なるロジックを適用します。

BicycleクラスのmaxSpeedメソッドをオーバーロードして、パラメーターを受け入れることができるようにすることで説明しましょう。

def maxSpeed(speedLimit: Int): Int = {
  bikeType match {
    case "road" => if(speedLimit < 60) speedLimit else 60
    case _ => if(speedLimit < 20) speedLimit else 20
  }
}

次に、maxSpeedメソッドのいずれかを使用した場合の違いを確認できます。

val bicycle = new Bicycle("red", "road")
assert(bicycle.maxSpeed() === 60)
assert(bicycle.maxSpeed(10) === 10)

5.2. メソッドのオーバーライド

別のタイプのポリモーフィズムは、メソッドのオーバーライドです。 オーバーライドは、サブクラスで使用され、そのスーパークラスによって提供されるメソッドの実装を再定義します。 メソッドを定義する前に、キーワードoverrideを追加することでこれを行うことができます。

override def accelerate(): Unit = { println("Whoooosh") }

Accelerate メソッドをオーバーライドすることにより、このメソッドを呼び出すと、「VroomVroom」の代わりに「Whoooosh」が出力されるようになります。

6. 結論

このチュートリアルでは、オブジェクト指向プログラミングの概念と、Scalaでそれらを実現する方法について説明しました。

いつものように、このチュートリアルで使用されているすべての例は、GitHubから入手できます。