成功的组件模型都依赖于非常简单的基础,这些基础使得组件能够创建、生成更加复杂的结构。
面向对象编程从未建立过这种基础性、通用的标准,其中组件的基础单位是对象,但对象还不够底层。每个开发者都会为客户创造一个新的“标准”,没有任何团队能为客户应该有哪些字段达成一致,因为它们需要满足客户在不同场景下的不同需求,因而需要不同的数据和运算方式。
在函数式编程语言的集合类型中,像List、VectorMap等,它们都共享一些共同的操作,这些操作大多定义在Seq这个抽象特征中。除了foreach操作外,所有操作都是纯净的高阶函数,没有副作用,且都接受函数作为参数,具体工作由该函数完成。这种高阶函数与离散数据中的组合器(combinator)概念非常接近。
我们可以将这些组合器串联起来,从而用很少的代码完成复杂的功能。对于特定问题,我们可以将数据和需要实现的行为分离。这与通常的面向对象编程的方法正好相反,面向对象总是将数据和行为绑定在一起。在自定义的类中创建所需逻辑的临时实现,是面向对象的典型做法
In [4]:
case class Employee (
name: String,
title: String,
annualSalary: Double,
taxRate: Double,
insurancePremiumsPerWeek: Double)
In [5]:
val employees = List(
Employee("Buck Trends", "CEO", 200000, 0.25, 100.0),
Employee("Cindy Banks", "CFO", 170000, 0.22, 120.0),
Employee("Joe Coder", "Developer", 130000, 0.20, 120.0)
)
定义Employee类放置雇员的字段类型,实际应用中,这些数据可能从数据库中加载。Employee类型很简单,只需要定义少量行为,在经典的面向对象设计中,我们可能会给Employee添加很多行为,用于工资计算或实现其他域的逻辑。在这里所选择的设计提供了最佳的分离,同时这种设计非常简洁。如果Employee的结构发生变化,需要修改代码时,维护的成本非常小。
In [6]:
// 计算每周工资单
val netPay = employees map { e =>
val net = (1.0-e.taxRate) * (e.annualSalary / 52.0) -
e.insurancePremiumsPerWeek
(e, net)
}
In [8]:
// 打印工资单
println("** Paychecks:")
netPay foreach {
case (e, net) => println(f" ${e.name+':'}%-16s ${net}%10.2f")
}
In [9]:
// 生成报表
val report = (netPay foldLeft (0.0, 0.0, 0.0)) {
case ((totalSalary, totalNet, totalInsurance), (e, net)) =>
(totalSalary + e.annualSalary / 52.0,
totalNet + net,
totalInsurance + e.insurancePremiumsPerWeek)
}
In [10]:
println("\n** Report:")
println(f" Total Salary: ${report._1}%10.2f")
println(f" Total Net: ${report._2}%10.2f")
println(f" Total Insurance: ${report._3}%10.2f")