Scala 3 — 書籍

網域建模

語言

Scala 支援函式程式設計 (FP) 和物件導向程式設計 (OOP),以及這兩種範例的融合。本節提供 OOP 和 FP 中資料建模的快速概觀。

OOP 網域建模

使用 OOP 風格撰寫程式碼時,資料封裝的兩個主要工具是特質類別

特質

Scala 特質可用作簡單的介面,但它們也可以包含抽象和具體的方法與欄位,並且可以像類別一樣具有參數。它們提供了一個絕佳的方式,讓您可以將行為組織成小型、模組化的單元。稍後,當您想要建立屬性和行為的具體實作時,類別和物件可以延伸特質,混合所需的各種特質以達成預期的行為。

作為使用特質作為介面的範例,以下是三個特質,定義了像狗和貓等動物的井然有序且模組化的行為

trait Speaker {
  def speak(): String  // has no body, so it’s abstract
}

trait TailWagger {
  def startTail(): Unit = println("tail is wagging")
  def stopTail(): Unit = println("tail is stopped")
}

trait Runner {
  def startRunning(): Unit = println("I’m running")
  def stopRunning(): Unit = println("Stopped running")
}
trait Speaker:
  def speak(): String  // has no body, so it’s abstract

trait TailWagger:
  def startTail(): Unit = println("tail is wagging")
  def stopTail(): Unit = println("tail is stopped")

trait Runner:
  def startRunning(): Unit = println("I’m running")
  def stopRunning(): Unit = println("Stopped running")

給定這些特質,以下是 Dog 類別,它擴充所有這些特質,同時提供抽象 speak 方法的行為

class Dog(name: String) extends Speaker with TailWagger with Runner {
  def speak(): String = "Woof!"
}
class Dog(name: String) extends Speaker, TailWagger, Runner:
  def speak(): String = "Woof!"

請注意類別如何使用 extends 關鍵字擴充特質。

同樣地,以下是 Cat 類別,它實作這些相同特質,同時也覆寫它繼承的兩個具體方法

class Cat(name: String) extends Speaker with TailWagger with Runner {
  def speak(): String = "Meow"
  override def startRunning(): Unit = println("Yeah ... I don’t run")
  override def stopRunning(): Unit = println("No need to stop")
}
class Cat(name: String) extends Speaker, TailWagger, Runner:
  def speak(): String = "Meow"
  override def startRunning(): Unit = println("Yeah ... I don’t run")
  override def stopRunning(): Unit = println("No need to stop")

這些範例顯示如何使用這些類別

val d = new Dog("Rover")
println(d.speak())      // prints "Woof!"

val c = new Cat("Morris")
println(c.speak())      // "Meow"
c.startRunning()        // "Yeah ... I don’t run"
c.stopRunning()         // "No need to stop"
val d = Dog("Rover")
println(d.speak())      // prints "Woof!"

val c = Cat("Morris")
println(c.speak())      // "Meow"
c.startRunning()        // "Yeah ... I don’t run"
c.stopRunning()         // "No need to stop"

如果這段程式碼有道理,很好,你已經熟悉特質作為介面。如果不是,別擔心,它們在 網域建模 章節中有更詳細的說明。

類別

Scala 類別用於 OOP 風格的程式設計。以下是模擬「人」的類別範例。在 OOP 中,欄位通常是可變的,因此 firstNamelastName 都宣告為 var 參數

class Person(var firstName: String, var lastName: String) {
  def printFullName() = println(s"$firstName $lastName")
}

val p = new Person("John", "Stephens")
println(p.firstName)   // "John"
p.lastName = "Legend"
p.printFullName()      // "John Legend"
class Person(var firstName: String, var lastName: String):
  def printFullName() = println(s"$firstName $lastName")

val p = Person("John", "Stephens")
println(p.firstName)   // "John"
p.lastName = "Legend"
p.printFullName()      // "John Legend"

請注意類別宣告會建立建構函式

// this code uses that constructor
val p = new Person("John", "Stephens")
// this code uses that constructor
val p = Person("John", "Stephens")

建構函式和其他類別相關主題在 網域建模 章節中有說明。

FP 網域建模

在 FP 風格中撰寫程式碼時,你會使用這些概念

  • 代數資料類型定義資料
  • 特質用於資料上的功能。

列舉和總和類型

總和類型是 Scala 中模擬代數資料類型 (ADT) 的一種方式。

當資料可以用不同的選項表示時,它們就會被使用。

例如,披薩有三個主要屬性

  • 餅皮大小
  • 餅皮類型
  • 配料

這些使用列舉精簡地模擬,列舉是僅包含單例值的總和類型

在 Scala 2 中,sealed 類別和 case object 結合起來定義列舉

sealed abstract class CrustSize
object CrustSize {
  case object Small extends CrustSize
  case object Medium extends CrustSize
  case object Large extends CrustSize
}

sealed abstract class CrustType
object CrustType {
  case object Thin extends CrustType
  case object Thick extends CrustType
  case object Regular extends CrustType
}

sealed abstract class Topping
object Topping {
  case object Cheese extends Topping
  case object Pepperoni extends Topping
  case object BlackOlives extends Topping
  case object GreenOlives extends Topping
  case object Onions extends Topping
}

Scala 3 提供 enum 建構來定義列舉

enum CrustSize:
  case Small, Medium, Large

enum CrustType:
  case Thin, Thick, Regular

enum Topping:
  case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions

一旦你有了列舉,就可以將其成員匯入為一般值

import CrustSize._
val currentCrustSize = Small

// enums in a `match` expression
currentCrustSize match {
  case Small => println("Small crust size")
  case Medium => println("Medium crust size")
  case Large => println("Large crust size")
}

// enums in an `if` statement
if (currentCrustSize == Small) println("Small crust size")
import CrustSize.*
val currentCrustSize = Small

// enums in a `match` expression
currentCrustSize match
  case Small => println("Small crust size")
  case Medium => println("Medium crust size")
  case Large => println("Large crust size")

// enums in an `if` statement
if currentCrustSize == Small then println("Small crust size")

以下是使用 Scala 建立總和類型的另一個範例,這不會稱為列舉,因為 Succ 案例有參數

sealed abstract class Nat
object Nat {
  case object Zero extends Nat
  case class Succ(pred: Nat) extends Nat
}

總和類型已在本書籍的 網域建模 區段中詳細介紹。

enum Nat:
  case Zero
  case Succ(pred: Nat)

列舉已在本書籍的 網域建模 區段和 參考文件 中詳細介紹。

乘積類型

乘積類型是一種代數資料類型 (ADT),只有一個形狀,例如單例物件,在 Scala 中以 case 物件表示;或具有可存取欄位的不可變結構,以 case 類別表示。

case 類別具備 class 的所有功能,而且還內建其他功能,使其適用於函數式程式設計。當編譯器在 class 前面看到 case 關鍵字時,會產生下列效果和好處

  • 預設情況下,case 類別建構函數參數是公開的 val 欄位,因此欄位是不可變的,而且會為每個參數產生存取方法。
  • 會產生 unapply 方法,讓您可以在 match 表達式中以更多方式使用 case 類別。
  • 會在類別中產生 copy 方法。這提供一種方式,可以在不變更原始物件的情況下建立物件的更新副本。
  • 會產生 equalshashCode 方法,以實作結構相等性。
  • 會產生預設 toString 方法,這有助於除錯。

可以手動將所有這些方法新增到類別中,但由於這些功能在函式程式設計中非常常用,因此使用 case 類別會更方便。

此程式碼展示了幾個 case 類別功能

// define a case class
case class Person(
  name: String,
  vocation: String
)

// create an instance of the case class
val p = Person("Reginald Kenneth Dwight", "Singer")

// a good default toString method
p                // : Person = Person(Reginald Kenneth Dwight,Singer)

// can access its fields, which are immutable
p.name           // "Reginald Kenneth Dwight"
p.name = "Joe"   // error: can’t reassign a val field

// when you need to make a change, use the `copy` method
// to “update as you copy”
val p2 = p.copy(name = "Elton John")
p2               // : Person = Person(Elton John,Singer)

請參閱 網域建模 部分,以取得有關 case 類別的更多詳細資訊。

此頁面的貢獻者