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