对于一个互联网公司的程序,代码的范式是很重要的,好的范式可以让你面对产品经理的指
示的时候痛苦感会降低, 这个时候架构就非常重要了。那么我将以 scala 为例,支持多范式
的语言不多,scala 是各种范式都能 hold 比较好的。展示一下 FP 和 OOP 的优缺点。
我们对架构先提出几点要求
- 拓展起来不会影响现有代码的清晰度
- 拓展起来的改动比较少
- 代码简单易测
假如有一天产品告诉你,来我们搞个计算器吧。第一期要实现加法和减法。那么你考虑的就
是这个:
(2 +3 ) + 4
用 scala 程序表示起来, 就是要求值下面这个东西。
1
| val expression = Add(Add(Number(2), Number(3)), Number(4))
|
Round One
OOP
首先来了一个 OOP 程序员,拿起 IDE,三军未动粮草先行, 先来个接口爽一下。
1
2
3
| trait Expr {
def eval: Double
}
|
emm,不错,之后在快速继承一遍
1
2
3
4
5
6
7
8
|
case class Number(value: Double) extends Expr {
override def eval = value
}
case class Add(a: Expr, b: Expr) extends Expr {
override def eval = a.eval + b.eval
}
|
下面是见证奇迹的时刻
1
| expression.eval //res0: Double = 9.0
|
完美!
FP
接着来了一个 FP 程序员,嘴里念叨着 Functional core imperative shell, 首先把数据结构
和计算拆开来了
1
2
3
4
|
trait Expr
case class Number(value: Double) extends Expr
case class Add(a: Expr, b: Expr) extends Expr
|
emm,纯数据结构清爽
1
2
3
4
5
| def evaluate(expression: Expr): Double = expression match {
case Number(a) => a
case Add(a, b) => evaluate(a) + evaluate(b)
}
|
emm, 纯计算,清爽
下面是见证奇迹的时刻
1
| evaluate(expression) //res0: Double = 9.0
|
完美!
Round 2
这个时候产品说了,这个计算器有点丑啊,能不能打印好看点
OOP
OOP 程序员熟练的修改了接口(改动+1)
1
2
3
4
| trait Expr {
def eval: Double
def prettyPrint: String
}
|
然后(改动+1 + 1) = 3 处
1
2
3
4
5
6
7
8
9
10
|
case class Number(value: Double) extends Expr {
override def eval = value
override def print = value.toString
}
case class Add(a: Expr, b: Expr) extends Expr {
override def eval = a.eval + b.eval
override def print = s”(${a.print} + ${b.print})”
}
|
FP
这就是加个方法的事儿(改动 +1) = 1 处
1
2
3
4
5
|
def print(expression: Expr): String = expression match {
case Number(a) => a.toString
case Add(a, b) => s”(${print(a)} + ${print(b)})”
}
|
Round 3
产品又说了,只有加法不够呀,我们还需要个减法
OOP
就是再实现一次接口呗(改动 +1)
1
2
3
4
| case class Neg(a: Expr) extends Expr {
override def eval = — a.eval
override def print = s”-${a.print}”
}
|
FP
首先要拓展一个数据结构
1
| case class Neg(a: Expr) extends Expr
|
然后所有的函数都要随之改动
1
2
3
4
5
6
7
8
9
10
11
12
|
def evaluate(expression: Expr): Double = expression match {
case Number(a) => a
case Add(a, b) => evaluate(a) + evaluate(b)
case Neg(a) => — evaluate(a)
}
def print(expression: Expr): String = expression match {
case Number(a) => a.toString
case Add(a, b) => s”(${print(a)} + ${print(b)})”
case Neg(a) => s”-${print(a)}”
}
|
结论
没有银弹,OOP 范式下数据结构的扩展成本比较低,FP 范式下算子的扩展成本比较低。