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 用于面向对象编程。下面是一个对“人”进行建模的类的示例。在面向对象编程中,字段通常是可变的,因此 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 类的更多详细信息,请参阅 域建模 部分。

此页面的贡献者