封不平聽在耳裏,暗叫:「到這地步,我再能隱藏甚麼?」仰天一聲清嘯,斜行而前,長劍橫削直擊,迅捷無比,未到五六招,劍勢中已發出隱隱風聲。他出劍愈來愈快,風聲也是漸響,劍鋒上所發出的一股勁氣漸漸擴展,旁觀衆人只覺寒氣逼人,臉上、手上被疾風颳得隱隱生疼,情不自禁的後退,圍在相鬥兩人身周的圈子漸漸擴大,竟有四五丈方圓。泰山派的一個道士在旁說道:「氣宗的徒兒劍法高,劍宗的師叔內力強,這到底怎麼搞的?華山派的氣宗、劍宗,這可不是顛倒來玩了麼?
《笑傲江湖》中的「劍宗餘孽」封不平本想仗着有嵩山派撐腰,一舉奪了華山掌門寶座。可打了半天劍法上佔不了便宜,最後只能使出「狂風快劍」,企圖之內力取勝。可見,任何高明武功若無內功心法相輔,也是徒勞無功。javascript
說回前端,現在的前端技術棧就如同武俠小說中的江湖同樣,各門各派自成一體,可謂「百花齊放」、「百家爭鳴」。html
這邊 React 、Vue 、AngularJS 、JQuery 誰還都談不上能一統江湖。「武林新貴」 Flux 、Redux 、Mobx 們已經忙着爭奪誰是數據流框架老大。Native 端 RN 剛偃旗息鼓,Weex 就大有「 I'm the everywhere 」之勢。連備受爭議的 GraphQL 內部都還有 Apollo、Relay 掐來掐去。前端
常聽到身邊的前端工程師抱怨,上週剛發佈的 XXX 新版本文檔還沒看,今天 YYY 公司又發佈了新框架,到底先學哪一個?其實,不管是哪一種框架哪項技術都是解決實際業務需求的手段、方法,和武林中各門各派的武功招式是同樣的,各有所長,各有各的獨到之處。java
咱們學習技術,除了瞭解具體使用方法,還須要掌握技術背後的設計理念和工程思想,這些背後的東西是咱們技術選型的依據,是架構設計的基礎,是軟件系統的靈魂。這就比如是的武功中「內功心法」催動拳腳刀槍,一招一式,虎虎生風,縱有大敵當前,亦是淡然自若。react
接下來分別談一下三種工程思想,分別是:「開閉原則」、「函數式編程」和「消息機制」,這三種工程思想在後端開發中均有普遍的使用,容易被你們忽略的是目前不少前端技術框架也應用了這三種思想,如下結合具體案例分析,但願可以幫助你們加深對技術自己的理解。git
說到面向對象設計,大部分人腦海中閃過的恐怕都是「23種設計模式」。設計模式表明的是業務場景中總結出的最佳實現方式,屬於實踐的範疇,在其之上是更爲重要的「SOLID」五大原則:編程
SOLID 五大原則的出發點也是軟件工程的終極目標:「高內聚、低耦合」。在後端開發中運用最多的是「依賴倒置原則」,與其相關的設計模式大約有5-6個。以下圖所示:redux
上圖也能夠理解爲從抽象概念到具體實踐的逐步演進。c#
在前端技術框架中,運用最多的是「開放封閉原則」,咱們先來看一下這條原則是怎麼定義的:後端
A software artifact should be open for extension but closed for modification.
翻譯過來就是:軟件系統應當對擴展開放,對修改封閉(感受像沒說)。這裏舉一個簡單的例子來講明開閉原則,先幫助你們理解概念:
public abstract class Shape { public abstract double Area(); } public class Rectangle: Shape { public double Width { get; set;} public double Height { get; set;} public override double Area() { return Width*Height' } } public class Circle: Shape { public double Radius { get; set} public override double Area() { return Radius*Radius*PI; } } public double Area(Shape [] shapes) { doubel area = 0; foreach (var shape in shapes) { area += shape.Area(); } return area; }
上例中不管場景如何擴展,Area 函數都無需修改,每一個 Shape 類經過繼承接口和多態特性,各自實現面積計算。
總結一下開閉原則就是:軟件系統的核心邏輯都不該該輕易改變,不然會破壞系統的穩定性和增長測試成本。咱們應當創建合適的抽象並統一接口,當業務須要擴展時,咱們能夠經過增長實體類來完成。
接下來咱們看一個「開閉原則」在前端框架中的應用: Ant Design 組件庫中的 Form 表單組件。
和其它組件不一樣,Form 組件並無具體的形態,它更像是一個容器,提供了接入的標準,並提供了校驗、表單提交等功能。繪製表單中的一項以下所示:
<FormItem> {getFieldDecorator('userName', { rules: [{ required: true, message: 'Please input your username!' }], })(<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" /> )} </FormItem>
Ant Design 組件庫中已經提供個幾乎全部的常見表單組件,如:Select 、Checkbox 、Radio 、Cascader 等,但在實際業務中,咱們仍是會須要設計業務相關的表單項,Form 表單經過統一組件接口的方式知足了這個技術需求,具體規約以下:
自定義或第三方的表單控件,也能夠與 Form 組件一塊兒使用。只要該組件遵循如下的約定:
- 提供受控屬性 value 或其它與 valuePropName 的值同名的屬性。
- 提供 onChange 事件或 trigger 的值同名的事件。
- 不能是函數式組件。
這正是「開閉原則」的一個典型實踐案例,即表單核心邏輯(校驗、提交等)保持不變並封裝在 Form 組件中,自定義表單項只須要知足上述三條規約,就能平滑接入到 Form 組件中,和 Ant Design 原生組件契合在一塊兒。
Ant Design 中的 Form 組件經過這樣一個簡潔的設計,完美提供了表單類型頁面的統一解決方案。
隨着人工智能、區塊鏈、AR、VR、新零售等業務場景的出現,產品界面交互正在變得愈來愈複雜,這就對現代的前端開發者提出了更高的要求。如何快速、正確、高效地開發出高複雜度頁面是目前前端技術最須要解決的問題。
函數式編程(如下簡稱 FP )憑藉其高複用性、易測試性和與之帶來的健壯性和簡潔開始逐漸佔據前端技術圈,咱們發現愈來愈多的前端框架以 FP 爲設計核心準則。
咱們先簡單介紹一下 FP,函數式編程的特徵主要包括如下幾個方面:
JS 語言中的函數不一樣於 Java ,C/C++ 等語言, 能夠被當作參數和返回值進行傳遞,所以天生具有「一等公民」特性。「模塊化、組合」、「引用透明」、「避免狀態改變」、「避免共享狀態」這四個特徵都須要經過特定代碼模式實現。先舉兩個小例子:
var words = [], count = 0; var text = str.split(''); for (var i = 0; couont < 4, i < text.length; i++) { if(!text[i].match(/[0-9]/)) { words = words.concat(text[i]); count++; } }
var words = str.split('').filter(function(x){ return (!x.match(/[1-9]+/))}).slice(0,4);
第二段代碼中使用的 js 數組方法 filter 和 slice,去掉了 for 循環,代碼更簡潔流暢。在寫具體業務代碼的時候,「模塊化、組合」是 FP 最經常使用的技術,也是最重要的實現功能的手段。
function plus(array) { var res = array[0]; for (let i = 1; i < array.length; i++) { res += array[i]; } } function mul(array) { var res = array[0]; for (let i = 1; i < array.length; i++) { res *= array[i]; } } function and (array) { var res = array[0]; for (let i = 1; i < array.length; i++) { res = res & array[i]; } } plus(array); mul(array); and(array);
var ops = { "plus": (x,y)=>x+y, "mul" : (x,y)=>x*y, "and" : (x,y)=>x&y } function operation(op, array) { return array.slice(1).reduce(ops[op], array[0]); } operation("plus", array); operation("mul", array); operation("and", array);
後一段代碼中,使用了 reduce 函數代替了 for 循環,並將數值計算部分做爲模塊提取出來,當有新的計算類型時,只須要在 ops 對象中定義計算過程。這裏就體現了 FP 中「模塊化、組合」的特性。在 FP 風格下,咱們習慣將複雜邏輯切割成一個個小模塊,經過組合這些模塊實現新的業務功能,當有新的需求到來時,咱們儘量地複用已有模塊達到目標。FP 代碼在複用性方面相比 OOD 有明顯的優點。
React 框架中,當用戶操做 UI 或者 API 的返回帶來了數據的改變,React 隨即進行 virtual dom diff 計算獲得 dom 的修改指令,對 dom 元素應用修改指令便獲得最新的 html 界面,以下圖所示:
不難發現,React 實際上是應用數據對UI的一種映射,不一樣的數據會映射出不一樣樣式的 UI 界面,咱們能夠得出以下的表達式:
$UI = React(data)$
沒錯,React 的本質實際上是一種函數,而且仍是符合 FP 要求的「引用透明」函數。所謂「引用透明」就是指函數的輸出僅依賴函數參數,不受任何外部環境影響。這樣的函數可測試性強,也很是容易進行組合。
在 React 的體系下,任何組件均可由一個個更小的組件構成,每一個組件都只關心本身的輸入,他們不斷地接受新的數據並輸出對應的新的UI界面。React 框架中經常使用的「高階組件」能夠看做引用透明」函數的組合模式。
在具體業務中咱們一般還須要權衡 React 組件的複用性和開發體驗,若是組件被拆分的過於細,當然複用性會提高,但文件數量會增長,對應的文檔和溝通成本也會增長,這也是 FP 在實踐過程當中常常遭人詬病的點,即複用性提高後帶來的額外開發成本。
消息機制是軟件工程中一個廣泛運用的工程思想。「設計模式」中的觀察者模式、Windows 操做系統底層、Spring 框架中的 ApplicationListener 模塊、Objective-C 語言中的函數調用、都是經過消息機制驅動的。
使用消息機制最大的好處在於能夠作到業務模塊間安全解耦,模塊間經過發送消息的方式進行協做,咱們先舉一個後端開發中的例子,下圖是一個簡單的預約系統的建模圖,並無使用消息機制:
在沒有消息機制的狀況下,用戶模塊須要知道訂單模塊的存在,並向起進行接口調用,同理訂單模塊須要向支付模塊進行接口調用。這種設計下模塊間是耦合的。
咱們再來看一下使用消息機制的狀況:
上圖中,不管是客戶下訂單、支付仍是預約都是經過消息的方式傳遞的,每一個模塊都是向一個消息處理器起發消息,同時也監聽消息處理器發送回來的消息。在這種模式下,模塊徹底不知道其它模塊的存在,完全作到了解耦。
在前端業務開發中,咱們常常也會用到 EventEmitter 庫來進行消息傳遞。好比頁面上有兩塊區域,一塊用 React 框架渲染,一塊用 D3 渲染的,當兩塊區域須要數據同步時,就可使用消息機制進行通信,保證頁面數據總體一致。
若是你的業務中有不一樣生命週期的組件,建議採用消息機制進行管理,不只消除了耦合,邏輯關係部分的代碼也集中到了一個文件中,內聚性獲得了提高。
使用消息機制的一個附屬產物就是中間件,咱們能夠爲消息定製各類中間件,在中間中完成一些通用邏輯,讓業務代碼更精煉。
說到前端框架中消息機制的運用,固然首推 Redux 框架,在 Redux 框架中,任何數據交互都須要先轉化爲一個 action,由 action 去觸發 reducer 和相關的 middleware 處理 action,改變數據,最終同步到頁面 UI 上,以下圖所示:
關於使用 Redux 的種種利弊,在各大社區中都有很深刻的討論,本文再也不贅述。
「開閉原則」、「函數式編程」、「消息機制」這個三個軟件工程中重要的思想方法比如三套內功口訣,掌握了他們,才能更深入地理解技術框架自己,發揮出技術框架的最大威力。
寫到這裏忽然又想起《天龍八部》中一段:
喬峯眼見旁人退開,驀地心念一動,呼的一拳打出,一招「衝陣斬將」,也正是「太祖長拳」中的招數。這一招姿勢既瀟灑大方已極,勁力更是剛中有柔,柔中有剛,武林高手畢生所盼望達到的拳術完美之境,竟在這一招中表露無遺。
一套平平無奇的「太祖長拳」在喬峯手中盡能有如此氣象!
多少年之後,每當人們聊起金庸,聊起那個武俠世界,想必都會津津有味地回味、談論起聚賢莊中這石破天驚的一拳。
文章可隨意轉載,但請保留此 原文連接。
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj(at)alibaba-inc.com 。