OOP(面向对象)与 FP(函数式)风格

9 minutes read
JavaScriptVueTypeScript架构设计

编程范式的选择

最近考察Vue3,发现vue3架构改动幅度很大,当前对于TS的支持度提升很多,从早期基于对象的编程(OOP)转向了函数式编程(FP)。

函数式编程(Functional Programming, FP)是一种编程范式——一种构建计算机程序结构和元素的方式,它将计算视为数学函数的评估,并避免改变状态和可变数据。

面向对象编程(Object-oriented Programming, OOP)是一种基于"对象"概念的编程范式,它可以包含字段(通常称为属性或属性)形式的数据,以及过程(通常称为方法)形式的代码。

计算机的著名定论:程序 = 数据结构 + 算法,无论是什么范式,都必须处理数据结构和算法。

OOP(面向对象编程)

OOP 会把数据和相关行为放到某个对象中处理,所谓的对象就是数据与行为的集合,是对现实某个实体的抽象,比起数据,对象更关心实体有哪些行为,比如Java/PHP/JS中的class。

OOP的核心特点

  • 抽象(关注本质,隐藏不必要的细节)
  • 继承(根据另一个类定义一个类)
  • 多态性(组合元素以创建新实体)
  • 封装(它允许对用户隐藏不相关的数据并防止未经授权的访问)
export default class Person extends AnyThing {
    private age: number = 10;
    week() {} // 公共方法
    private _clear() {} // 私有方法,外部无法访问
}

对象一般就是某个类的实例new Person,实例中的数据一般不会暴露给外部代码,外部代码能接触到的只有实例提供的方法。

OOP的优点

  • 数据保护:保护部分方法不被外部使用。隐藏类中的变量,从而防止外部访问。
  • 模块化:OOP允许模块化(将程序的功能分成独立模块的可能性)和共享状态的管理。
  • 代码复用:对象可在另一个应用程序中重用。为同一个类创建新对象很容易,容易维护和修改代码。
  • 内存管理:在创建大型程序时,它提供了很大的好处,因为它可以轻松地将事物分成更小的部分,并有助于区分需要以某种方式执行的组件。

OOP的缺点

不可重用性:由于某些函数依赖于使用它们的类,因此它们很难与其他类一起使用。除此之外,它的效率较低且更难处理。许多面向对象的程序也用于模拟大规模架构,并且可能很复杂。

FP(函数式编程)

FP 是通过组合纯函数来构建软件的过程。所有对象都是不可变的,这意味着一旦创建了某些东西,就无法更改它。

把数据和行为完全分开处理,比起行为,FP 更加关注数据结构是什么样的,怎么样由 A 数据衍生计算出 B 数据,代表语言如Go/F#。FP 适合数据结构稳定清晰的场景,数据驱动开发模式就是基于 FP 范式的。FP 带来的很大好处是数据不可变,由于数据不可变,开发者不用关心什么某个数据在什么时候什么条件下会改变。

FP的核心特点

  • 函数是第一等公民:函数可以作为参数传递、作为返回值返回
  • 强调将计算过程分解成可复用的函数
  • 纯函数:只有纯的、没有副作用的函数,才是合格的函数
// 函数式编程示例
const add = (x, y) => x + y
const pow = (x) => x * x
const val = pow(add(2, 3)) // (2 + 3)^2 = 25

函数式编程的基本运算

函数合成

如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。

const compose = (f, g) => (x) => f(g(x))
// 函数的合成满足交换律和结合律,以下调用方式等价
compose(f, compose(g, h))
compose(compose(f, g), h)
compose(f, g, h)

柯里化

f(x)g(x)合成为f(g(x)),有一个隐藏的前提,就是fg都只能接受一个参数。如果可以接受多个参数,比如f(x, y)g(a, b, c),函数合成就非常麻烦,所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

// 柯里化之前
const add = (x, y) => x + y
add(1, 2) // 3
 
// 柯里化之后
const addN = x => y => x + y
addN(2)(3) // 5
 
// 柯里化的实际应用
const discount = rate => price => price * rate
const tenPercentOff = discount(0.1) // 创建一个10%折扣函数
const twentyPercentOff = discount(0.2) // 创建一个20%折扣函数

FP的优点

  • 可预测性:提供了诸如惰性求值、无错误代码、嵌套函数、并行编程等优点
  • 声明式编程:使用更具声明性的方法,专注于需要做的事情而不是如何去做,并强调效率和优化
  • 追踪变化:在函数式编程中,由于对象本身现在是具有不同名称的新对象,因此更容易了解进行了哪些更改
  • 适合特定场景:当不需要边界或已经预定义边界时,它可以很好地工作
  • 数学模型:在函数式编程中,模拟现实世界的过程比模拟对象更容易。它的数学起源使其适用于需要计算或任何包括转换和处理的情况

FP的缺点

  • 学习曲线:需要采用不同的思维方式来编写代码。虽然用面向对象的术语来思考很容易,但将现实世界的场景转换为纯函数需要更多的脑力劳动
  • 社区较小:由于 FP 更难学习,因此以这种方式编程的人较少,关于该主题的信息自然较少

如何选择编程范式

两种范式共同目标是开发可理解且无错误的程序,但它们的方法不同。普遍的共识是 OOP 和 FP 在任何特定情况下都是有效的,因此开发人员始终可以选择使过程高效且简单的编程范式。

选择范式时应考虑项目需求、团队熟悉度、性能要求和代码维护成本等因素。

实现原理:Vue2 与 Vue3 的范式转变

Vue2 到 Vue3 最直观的改变就是 Composition API——几乎所有的 Vue2 options 方法都被放到了 setup 函数里:

+ import { onMounted, reactive, watchEffect } from 'vue'
 
export default {
  name: "App",
 
+  setup( props ) {
+    const state = reactive({ /*...*/ });
+    onMounted(() => { /*...*/ });
+    watchEffect(() => { /*...*/ });
+    return { state };
+  },
 
-  data: () => ({ state: /*...*/ }),
-  mounted(){ /*...*/ },
-  watch: { /*...*/ },
};

这是一个比较大的风格转变,通俗来说,就是从基于对象的编程(OOP)转向了函数式编程(FP)。

在选择编程范式时,可以遵循以下建议:

  1. 混合使用:在实际项目中,往往可以混合使用OOP和FP的概念
  2. 按场景选择:数据处理和转换任务使用FP,实体建模使用OOP
  3. 团队一致性:保持团队内部编码风格一致性比选择哪种范式更重要
  4. 渐进采用:如从Vue2迁移到Vue3,可以渐进式地采用Composition API
  5. 关注可测试性:无论选择哪种范式,都应该编写易于测试的代码

参考

On this page

Scroll to top