函数式编程与面向对象编程

我们接触函数式和面向对象概念这两个概念往往是来自对编程语言的认知,大部分编程语言会给自己贴上 函数式 或者面向对象的标签。 比如Ruby,是一门非常纯粹的面向对象编程的语言。

本质上来说,函数式编程和面向对象编程,都是编程的一种方法。给自己贴上对应标签的程序语言,更准确的说法是,更适合某种编程范式的语言。

那什么是函数式编程和面向对象编程呢?

函数(方法)是否在对象上调用?

以 Python 为例,有些函数是全局函数,不在任何对象上调用。如:

print('test')

有些函数,则是需要在某个对象上调用,如:

'A'.lower()

这个例子揭示了面向对象编程和函数式编程最本质的区别。

函数式编程是“无状态”的,输出的结果只取决于输入的参数。

而面向对象编程的函数调用,对象本身和传入参数都会直接参与函数的运算过程中。

在对象上调用的函数该对象一定会参与函数运算么?

以Ruby为例,我们在一个字符串对象 ‘a’ 上定义一个单例方法

a = 'a'

def a.sum(x, y)
  x + y
end

a.sum(1, 1) 
# => 2

你肯定觉得我这个做法很二,这个方法跟这个对象一点关系都没有,干嘛要在这个对象上定义这样的方法。

在这个例子中,表面上是个面向对象编程(在某个对象上调用),实际上是个函数式编程风格的代码。

函数式编程和面向对象编程的优劣

我们将上面的例子进行一个改造。

a = 1

def 1.sum(y)
  self + y
end

1.sum(1)
# => 2

这是一个典型的面向对象编程风格的例子。跟上面的代码相比,很明显代码量减少了。面向对象是对程序员的解放,迎合了程序员都是懒人的天性,真是nice呀。

既然面向对象这么好,那函数是编程存在的意义是什么呢?

  1. 稳定,易于测试 在函数式风格中,运算结果只取决于传入的参数,传入的参数是什么,非常容易观察到,那结果也是容易推导出来。大大降低了程序员出错的概率。

  2. 性能更高 在面向对象语言中,对象的生成(实例化)是会消耗内存和计算资源的。所以函数式编程语言的性能相对于面向对象往往性能更高。

Ruby是纯粹的面向对象语言

熟悉ruby的程序员应该知道,Ruby号称是一门非常纯粹的面向对象的程序语言,在ruby中,“一切都是对象”,方法只会在对象上进行调用。

好像有人提出反对,你看例子:

p '^_^'

这个方法就没有在对象上进行调用呀。

其实Ruby只是隐藏了某些细节,让你觉得这个不是在对象上调用的而已。 在ruby中,如果没有指定调用对象,则会使用 self 调用该方法,而self是谁取决于上下文环境。

self.p '^_^'

# NoMethodError (private method `p' called for main:Object)

这个例子中,方法p是个私有方法,如果指定对象调用会报错。但是这个报错,正好告诉了我们真谛:方法p也是在某个真实的对象上调用的。

ruby里是不存在脱离对象调用的方法的,不像python,python里的全局函数是脱离对象存在的。

所以说js是不纯粹的。

Ruby是最方便用函数式编程风格的程序语言

由于 Ruby 中所有的方法都是在对象上调用,实现函数式风格亦不能违背这个准则,于是我们就需要找到一个尽可能“小”的对象。

如何找到尽可能“小”的对象

在回答这个问题之前,我们需要先知道对象中包含什么?

  • 其所属的类;
  • 在对象上定义的实例变量;

如何减少对象的开销

先看一个普通的 Ruby 对象(类)的实例化

o = Object.new

每次对象的实例化都是一次开销,所以我们用于函数式编程的傀儡对象最好是生成之后,就可以一直用,最好的变法就是定义一个全局的变量或者常量引用。

变量还是常量

在目前的 Ruby 解释器(2.7及以前)实现中,常量和变量本质上没有区别,除了在给常量重新赋值时会有警告。据说在 Ruby3.0 的版本中,对于常量会自动调用freeze方法,从而避免对常量的改变。

在对象上定义方法,而不是在类上

由于并没有多次实例化的需求,我们只需要在这个生成的对象上定义方法即可。 需要知道的是,在ruby中,方法实际是只能在类中定义的。在对象上定义的方法,实际都定义在该对象的 singleton_class 中。每个对象都有一个唯一属于它的 singleton_class。

沿着上述的思路,在ruby中最佳实践是通过 module 来实现。

module Helper
  extend self

  def show
    p self
  end
end