Scala for Backend Engineering & Functional Programming · 课时

组合领域模型

构建更大的模型

第 4 / 4 课13 个步骤

组合领域模型 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

构建更大的模型

真实领域模型由更小的部分组合而成。组合领域模型是指将建模良好的 ADT、新类型和经过验证的值组合成更大且仍然正确的结构。

  • 小而专注的类型可以组合成更大的类型。
  • 每个部分都维护自己的不变量。
case class Money(cents: Long)
case class Product(name: String, price: Money)

object Main:
  def main(args: Array[String]): Unit =
    println(Product("Book", Money(1999)))

嵌套积类型

通过嵌套 case class 来组合积类型。每一层都会增加结构,同时保持可读性。

case class Address(city: String, zip: String)
case class Customer(name: String, address: Address)

object Main:
  def main(args: Array[String]): Unit =
    val c = Customer("Ada", Address("London", "E1"))
    println(c.address.city)

使用集合进行组合

使用 List、Map 和 Set 来建模领域内部的一对多关系。

case class Item(name: String, qty: Int)
case class Order(id: Int, items: List[Item])

object Main:
  def main(args: Array[String]): Unit =
    val order = Order(1, List(Item("pen", 2), Item("ink", 1)))
    println(order.items.map(_.qty).sum)

组合和类型

更大的选择由更小的选择组合而成。枚举变体本身可以包含另一个 ADT,从而将多个决策分层组织。

enum Payment:
  case Card(last4: String)
  case Cash

enum Checkout:
  case Completed(payment: Payment)
  case Abandoned

object Main:
  def main(args: Array[String]): Unit =
    val c: Checkout = Checkout.Completed(Payment.Card("1234"))
    println(c)

组合领域模型的函数

领域操作是从输入类型映射到输出类型的纯函数。组合这些函数可以构建工作流。

case class Cart(total: Long)
case class Receipt(total: Long, tax: Long)

object Main:
  def checkout(c: Cart): Receipt =
    Receipt(c.total, (c.total * 0.2).toLong)

  def main(args: Array[String]): Unit =
    println(checkout(Cart(1000)))

组合验证

当每个字段都由智能构造器创建时,可以使用 for 推导将它们组合起来。只有所有部分都通过验证,整个对象才会被创建。

object V:
  def name(s: String): Option[String] = if s.nonEmpty then Some(s) else None
  def age(n: Int): Option[Int] = if n >= 0 then Some(n) else None

case class Person(name: String, age: Int)

object Main:
  def make(n: String, a: Int): Option[Person] =
    for nm <- V.name(n); ag <- V.age(a) yield Person(nm, ag)

  def main(args: Array[String]): Unit =
    println(Main.make("Bob", 30))
    println(Main.make("", 30))

折叠组合数据

使用 fold,或结合 map 与 sum,来聚合组合结构。数据的形状会引导计算过程。

case class Line(price: Long, qty: Int)
case class Invoice(lines: List[Line])

object Main:
  def total(inv: Invoice): Long =
    inv.lines.foldLeft(0L)((acc, l) => acc + l.price * l.qty)

  def main(args: Array[String]): Unit =
    val inv = Invoice(List(Line(100, 2), Line(50, 4)))
    println(total(inv))

更新不可变模型

使用 copy 来组合变更;它会返回一个新值,并替换指定的字段。原始值保持不变。

case class Account(id: Int, balance: Long)

object Main:
  def deposit(a: Account, amt: Long): Account =
    a.copy(balance = a.balance + amt)

  def main(args: Array[String]): Unit =
    val a = Account(1, 100)
    println(deposit(a, 50))

建模状态机

组合和类型与状态转换函数,可以建模出只允许合法转换存在的状态机。

enum Light:
  case Red, Green, Yellow

object Main:
  def next(l: Light): Light = l match
    case Light.Red    => Light.Green
    case Light.Green  => Light.Yellow
    case Light.Yellow => Light.Red

  def main(args: Array[String]): Unit =
    println(next(Light.Red))
    println(next(Light.Green))

分层组织领域与操作

保持数据类型纯粹,并将行为放入函数或方法中。组合后的模型加上一组变换,就构成了一个小型领域库。

case class Temp(celsius: Double):
  def toF: Double = celsius * 9 / 5 + 32

object Main:
  def main(args: Array[String]): Unit =
    val t = Temp(25.0)
    println(t.toF)

综合运用

完整的模型会组合新类型、积类型、和类型、集合以及经过验证的构造过程。这样的结果难以被误用,也易于推理。

  • 先构建小型类型。
  • 将它们组合成聚合结构。
  • 将操作表达为纯函数。
case class Sku(value: String)
case class Line(sku: Sku, qty: Int)
case class Basket(lines: List[Line]):
  def count: Int = lines.map(_.qty).sum

object Main:
  def main(args: Array[String]): Unit =
    val b = Basket(List(Line(Sku("A"), 2), Line(Sku("B"), 3)))
    println(b.count)

快速检查

测试您对组合领域模型的理解。

回顾

您已经学会了如何组合领域模型。

  • 嵌套积类型和和类型,以建模丰富的结构。
  • 使用集合表示一对多关系。
  • 使用 for 推导组合验证过程。
  • 使用 copy 以不可变方式更新数据。
  • 保持数据纯粹,并将操作表达为函数。
case class User(id: Int, name: String)
case class Post(author: User, text: String)

object Main:
  def main(args: Array[String]): Unit =
    val p = Post(User(1, "Ada"), "hello")
    println(p.copy(text = "hi"))
免费开始

用 AI 导师学习 Scala — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
39
课程
143

常见问题解答

「组合领域模型」课时是免费的吗?

是的 — 「组合领域模型」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

「组合领域模型」这节课中我会学到什么?

构建更大的模型 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「组合领域模型」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?

能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用代数数据类型建模
  2. 智能构造器
  3. 新类型
  4. 组合领域模型
← 返回 Scala for Backend Engineering & Functional Programming