平时开发应该怎么选择函数式编程还是面向对象编程?

1,011 阅读5分钟

如果你感觉我的内容还不错的时候,请给小编来个赞或者关注。

(functional programming)最近一直是很火的概念,但是这种概念已经诞生了60余年。当今最好的Python,Ruby,Javascritp对函数式编程支持都很强,就连老牌的面向对象JAVA、PHP都在迭代对匿名函数的支持。

函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入使用。单元测试是最好的例子。

函数式编程缺乏通俗的入门教程,各种介绍文章都充斥着数学符号和专用术语,让人读了如坠云雾。

什么是函数式编程呢?

主要是思想是把运算过程尽量写成一系列嵌套的函数调用。

比如一个简单的表达方式

( 1 + 2 ) * 3 - 4

如果使用编程的步骤表达

var a = 1 + 2
var b = a * 3
var c = b - 4

如果我们把以上的表达方式用函数的形式表达

var result = subtract(multiply(add(1,2),3),4)
add(1,2).multiply(3).subtract(4)  

函数编程的一个核心就是每一步都是单纯的运算,而且都有返回值。这就有点想管道了比如rxjs里的操作法pipe的这种方式。

merge([1,2],[3,4]).sort().search("2")

在函数编程里面我们不得不了解一个概念和一个面试问得最多的问题。

柯里化

几年前面试必考的题目

如果ui开发现在组件是我们的最小单元,那么函数式编程,函数是我的最小单元。我们在开发的过程中会把一个逻辑拆分多个函数并进行单元测试,最后通过组合的形式变成我的的一个业务逻辑操作。

比如f(x)和g(x)组合成 f(g(x)),就是f和g都只能接受一个参数。如果可以接受多个参数,比如f(x, y)和g(a, b, c),函数合成就非常麻烦。

这时就需要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

有了柯里化我们可以转换成以下的方式

function addX(y){
	return function (x) {
    retruen x + y
  }
}

const addX = (y) => (x) => x + y;

addX(2)(1)

这里这是一个简单的例子,至于最后要给什么完全又你实现怎么样的柯里化函数。

组合

上面也说过,我们的逻辑是需要把多个函数进行组合变成一个大的业务处理。有了上面的例子,再看组合就不是什么难事了

const compose = (f, g) => (value) => f(g(value));

function reverse (array) {
  return array.reverse()
}

function first (array) {
  return array[0]
}

//我们先倒叙再获取第一个值
const last = compose(first,reverse)

console.log(last([1,2,3,4,5,6,7]))

当然我们很多时候写代码不会这么去做,一般都是使用后端的controller->service->api的概念去封装。

function compose() {
		let values = form.getFiletValues()
    
  	values = format(values)
 
  	if(!EmailNotNull(value.email)){
    	 thew now Error('error msg')
    }
  
  	return FormatResult(PostRequest(values),200)
}

function EmailNotNull(email){
		return is email ? true : false;
}

function FormatResult(values,code){
  return {	
  	code,
    data:values
  }
}

这就是函数式编程的思路,把每一个东西都拆成一个小的东西,我们平时做单元测试的时候就校验小的函数会不会出问题,不出问题几乎也不会出什么大问题。遇到新的场景还可以进行组合。

比如 FormatResult EmailNotNull 就可以直接拿来使用。在新的业务场景里面调用即可!

与OOP(面向对象的差异)

OOP比较特殊的特性就是继承。上面也说过。我们的功能模块,可以拆开多个函数进行组合。由于函数是零散的,当我们开发一个新的功能的时候就需要进行组合。

那上面的例子来说吧,如果我现在需要进行微信登录和pc登录分别实现不同的FormatResult,那么上面的代码就好变成以下情况。当然这里代码比较简单,所以只需要一个参数就可以实现,但是如果你的一个场景变化太多的时候,这个时候compose改动就较大。

function compose(FormatResultWX) {
		let values = form.getFiletValues()
    
  	values = format(values)
 
  	if(!EmailNotNull(value.email)){
    	 thew now Error('error msg')
    }
  	
		//... more handle  已经组合了很多东西了自己脑补一下
  
  	return FormatResultWX(PostRequest(values),200)
}

function EmailNotNull(email){
		return is email ? true : false;
}

function FormatResultWX(values,code){
  return {	
  	code,
    data:values
  }
}

function FormatResultPC(values,code){
  return {	
  	code,
    data:values,
    msg:'success'
  }
}

如果使用OOP

class App() {
  	compose(){
      let values = form.getFiletValues()

      values = format(values)

      if(!EmailNotNull(value.email)){
         thew now Error('error msg')
      }
			
      this.moreHandle()
      
      return this.FormatResult(PostRequest(values),200)
    }	
  
  	FormatResult(values,200){}
  	
  	moreHandle(){}
}

class WxApp() extends App{
	FormatResult(values,code){
  	return {
    	code,
    	data:values
    }
  }
  //实现其他想重写的功能
	 moreHandle(){}
}

class PcApp() extends App{
	FormatResult(values,code){
  	return {
    	code,
    	data:values,
      msg:""
    }
  }
  //实现其他想重写的功能
	 moreHandle(){}
}

总结

其实两种思路都很不错,有的时候两者一起使用会效果更好。这种都是要你对业务和思维角度去考虑的。系统设计没有最好方案,只有最合适才是最完美的。如果你有做单元测试的话,那函数式编程的确是一个不错的选择。在测试用例模型中,不需要考虑更多的依赖。就好像你调用api接口的时候不要考虑关联数据mock一样。