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 中,欄位通常是可變的,因此 firstName
和 lastName
都宣告為 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
案例有參數
乘積類型
乘積類型是一種代數資料類型 (ADT),只有一個形狀,例如單例物件,在 Scala 中以 case
物件表示;或具有可存取欄位的不可變結構,以 case
類別表示。
case
類別具備 class
的所有功能,而且還內建其他功能,使其適用於函數式程式設計。當編譯器在 class
前面看到 case
關鍵字時,會產生下列效果和好處
- 預設情況下,
case
類別建構函數參數是公開的val
欄位,因此欄位是不可變的,而且會為每個參數產生存取方法。 - 會產生
unapply
方法,讓您可以在match
表達式中以更多方式使用case
類別。 - 會在類別中產生
copy
方法。這提供一種方式,可以在不變更原始物件的情況下建立物件的更新副本。 - 會產生
equals
和hashCode
方法,以實作結構相等性。 - 會產生預設
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
類別的更多詳細資訊。