命令式、聲明式、面向對象、函數式、控制反轉之華山論劍(上)

命令式、聲明式、面向對象、函數式、控制反轉之華山論劍(上)

我接觸編程比較晚,從自學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,邏輯與邏輯處理的代碼徹底分離,代碼可讀性很是高。

既然本文介紹的主要是函數式編程,因此主觀評價了函數式的優勢。固然面向對象的編程模式優勢更加突出,各位客官已經很是熟悉封裝、繼承、多態給咱們帶來的優勢,代碼可讀性與可維護性在全部模式中名列前茅,面向對象編程位列神壇已久,在此沒必要多言。

相關文章
相關標籤/搜索