命令式、声明式、面向对象、函数式、控制反转之华山论剑(上)

命令式、声明式、面向对象、函数式、控制反转之华山论剑(上)

我接触编程比较晚,从自学java开始,面向对象的思想就已经深刻骨髓。以前那些年,个人代码只有这一种编码风格。前端

这些年来,js发生了翻天覆地的变化,前端已经远不是那个dom横行,ajax调用接口的时代。数据驱动、函数式(声明式编程)、工程化、Node、状态管理等大量新兴的技术进入眼帘。咱们亲眼见证前端代码从面向对象到函数式的转变,从抵制到接受,从学习到惊叹,惊叹一等对象的神奇,惊叹仅仅声明配置就能够完成功能,惊叹js竟然有这样的高玩。java

固然,我也见过太多的人对函数式嗤之以鼻,以为函数式编程难以维护,在业务复杂的场景下容易造成维护噩梦(asserts hell)。今天,以我我的的立场(彻底中立,不带任何面瘫色彩),就函数式编程与面向对象编程作简单的博弈,顺便介绍下从Java中spring框架就开始兴起的控制反转思想,这种思想的两个组成部分依赖注入依赖收集正式大名鼎鼎的angular的mvvm的实现原理。程序员

命令式与声明式的区别

咱们有两种编程方式:命令式和声明式。面向对象编程属于命令编程与声明式的结合。
  
咱们能够像下面这样定义它们之间的不一样:ajax

  • 命令式编程:命令“机器”如何去作事情(how),这样无论你想要的是什么(what),它都会按照你的命令实现。
  • 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去作(how)。

举个简单的例子,假设咱们想让一个数组里的数值翻倍。spring

命令式:编程

var numbers = [1,2,3,4,5]
var doubled = []
for(var i = 0; i < numbers.length; i++) {
  var newNumber = numbers[i] * 2
  doubled.push (newNumber)
}
console.log (doubled) //=> [2,4,6,8,10]复制代码

声明式json

var numbers = [1,2,3,4,5]
var doubled = numbers.map (function (n) {
  return n * 2
})
console.log (doubled) //=> [2,4,6,8,10]复制代码

map函数所作的事情是将直接遍历整个数组的过程概括抽离出来,让咱们专一于描述咱们想要的是什么(what)。注意,咱们传入map的是一个纯函数;它不具备任何反作用(不会改变外部状态),它只是接收一个数字,返回乘以二后的值。
  
在一些具备函数式编程特征的语言里,对于 list数据类型的操做,还有一些其余经常使用的声明式的函数方法。例如,求一个list里全部值的和,命令式编程会这样作:数组

var numbers = [1,2,3,4,5]
var total = 0 for(var i = 0; i < numbers.length; i++) {
  total += numbers[i]
}
console.log (total) //=> 15复制代码

而在声明式编程方式里,咱们使用reduce函数:闭包

var numbers = [1,2,3,4,5]
var total = numbers.reduce (function (sum, n) {
  return sum + n
});
console.log (total) //=> 15复制代码

reduce函数利用传入的函数把一个list运算成一个值。它以这个函数为参数,数组里的每一个元素都要通过它的处理。每一次调用,第一个参数(这里是sum)都是这个函数处理前一个值时返回的结果,而第二个参数(n)就是当前元素。这样下来,每此处理的新元素都会合计到sum中,最终咱们获得的是整个数组的和
架构

一样,reduce函数概括抽离了咱们如何遍历数组和状态管理部分的实现,提供给咱们一个通用的方式来把一个list合并成一个值。咱们须要作的只是指明咱们想要的是什么?

声明式编程为何让某些人疑惑,不屑,甚至排斥?

从声明式编程诞生的那天起,对声明式编程与命令式编程的讨论就没有中止过。做为程序员,咱们很是习惯去命令计算机去作某些事情。遍历列表,判断,赋值已是咱们逻辑中最多见的代码。

在不少状况中,命令式编程确实很是直观、简单而且编码运行效率最高,最重要的,维护的人也很是容易理解。加上大多数人并不理解函数的本质,只能把逻辑与数据封装到一个个对象中,以上的种种缘由,致使声明式编程一直没有成为主流的编程模式。甚至有人以为声明式编程是反人类思惟模式的编程,只是为了写一些所谓高大上的“玩具”产生的模式。

若是咱们花时间去学习声明式的能够概括抽离的部分,它们能为咱们的编程带来巨大的便捷。首先,我能够少写代码,这就是通往成功的捷径。其次,咱们能够抽象出很是实用的工具类,对对象或者函数进行深度加工,嵌套,运算,直到获得想要的结果。最后,每当有需求变动时候,大多数状况下,咱们无需改写框架(声明分析)代码,只须要修改声明的配置便可完成需求变动。

最重要的,它们能让咱们站在更高的层面是思考,站在云端思考咱们想要的是什么,什么是变化的,什么是不变的,找到变化,配置之,找到不变,封装之。最后你会发现,咱们不关心变化,由于变化的经过配置来声明,咱们只关心不变,也就是框架,用框架(不变)来处理声明(变化),正如道家的哲学,以不变(框架)应万变(声明)。而不是站在底层,思考事情该如何去作。

(一般来讲,核心的架构师编写不变的框架,低P/T编写配置声明,不要觉得配置仅仅是json等格式,在函数式编程里,配置每每是函数/类或者任何对象)

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

面向对象

将现实世界的物体抽象成类,每一个物体抽象成对象。用继承来维护物体的关系,用封装来描述物体的数据(属性)与行为(方法),经过封装技术,消息机制能够像搭积木的同样快速开发出一个全新的系统。既能够提升编程效率,又加强了代码的可扩展/维护等灵活性,是世界上运用最普遍的编程方法(我的观点:没有之一)。

面向对象语言是命令式编程的一种抽象。抽象包括两方面,数据抽象与过程抽象。在JS中,面向对象编程(也就是咱们常说的基于对象,由于JS并非面向对象的语言)把逻辑与数据封装到函数与原型中,经过函数的原型链拷贝实现继承,而代码的运行逻辑与数据依然封装在函数内,可是作了属性与方法的区分。优秀的面向对象编程显然能够作到声明式编程,也就是根据声明配置生成结果(也就是说,面向对象编程的逻辑是预设的,咱们能够根据输入条件,判断走不一样的逻辑)。

可是绝大多数的面向对象编程,不会根据声明配置去生成逻辑,逻辑的调用是封装在对象中,而不是动态生成。因此并无作到真正的声明式,也就是数据与逻辑彻底分离。这里所说的动态生成逻辑,是根据声明,自动完成逻辑的生成,这样就彻底能够不用编写业务代码,而仅仅靠声明来完成逻辑的实现,而这部分处理,交给框架处理便可。

函数式编程

把逻辑彻底视为函数的计算。把数据与逻辑封装到函数中,经过对函数的计算,加工,处理,来生成新的函数,最后拼装成一个个功能独立的函数。在运用这些函数,完成复杂逻辑的实现。

与现象对象不一样的是,咱们把数据和逻辑封装到函数中而不是类与对象中。每一个函数彻底独立,好的函数式设计,每一个函数都是一个纯函数(pure function,即输入固定参数,便可获得相同输入的函数)。优势是:

  • 面向对象中的任何一个原型方法(prototype)都会得到this的数据,并且能够轻易获取闭包的数据。这样的非纯函数让咱们很是难以提炼与抽象。
  • 纯函数因为输入与输出固定,因此变得很是容易单测。好的函数式中的函数设计,不会依赖于任何其余函数或者声明配置,只须要传递参数,既能够进行测试。而在面向对象语言中,咱们每每须要启动整个工程,或者说全部依赖的类所有要加载,才能开始测试。
  • 对逻辑作抽象与提取,让咱们避免在函数内作判断与循环,咱们只须要把具体处理封装到函数中,而程序运行过程当中的走向、判断与循环一般交给底层框架来处理。这让咱们彻底有能力动态生成逻辑。好比大名鼎鼎的d3和rx,逻辑与逻辑处理的代码彻底分离,代码可读性很是高。

既然本文介绍的主要是函数式编程,因此主观评价了函数式的优势。固然面向对象的编程模式优势更加突出,各位客官已经很是熟悉封装、继承、多态给咱们带来的优势,代码可读性与可维护性在全部模式中名列前茅,面向对象编程位列神坛已久,在此没必要多言。

相关文章
相关标签/搜索