作者:ES2049 Studio
https://www.yuque.com/es2049/blog/al62bl
前言
封不平聽(tīng)在耳里闸溃,暗叫:“到這地步,我再能隱藏甚么贪磺?”仰天一聲清嘯硫兰,斜行而前,長(zhǎng)劍橫削直擊寒锚,迅捷無(wú)比劫映,未到五六招,劍勢(shì)中已發(fā)出隱隱風(fēng)聲刹前。他出劍越來(lái)越快苏研,風(fēng)聲也是漸響,劍鋒上所發(fā)出的一股勁氣漸漸擴(kuò)展腮郊,旁觀眾人只覺(jué)寒氣逼人摹蘑,臉上、手上被疾風(fēng)刮得隱隱生疼轧飞,不由自主的后退衅鹿,圍在相斗兩人身周的圈子漸漸擴(kuò)大,竟有四五丈方圓过咬。泰山派的一個(gè)道士在旁說(shuō)道:“氣宗的徒兒劍法高大渤,劍宗的師叔內(nèi)力強(qiáng),這到底怎么搞的掸绞?華山派的氣宗泵三、劍宗,這可不是顛倒來(lái)玩了么衔掸?
《笑傲江湖》中的“劍宗余孽”封不平本想仗著有嵩山派撐腰烫幕,一舉奪了華山掌門(mén)寶座〕ㄓ常可打了半天劍法上占不了便宜较曼,最后只能使出“狂風(fēng)快劍”,企圖以?xún)?nèi)力取勝振愿〗萦蹋可見(jiàn)弛饭,任何高明武功若無(wú)內(nèi)功心法相輔,也是徒勞無(wú)功萍歉。
說(shuō)回前端侣颂,如今的前端技術(shù)棧就如同武俠小說(shuō)中的江湖一樣,各門(mén)各派自成一體枪孩,可謂“百花齊放”横蜒、“百家爭(zhēng)鳴”。
這邊 React 销凑、Vue 丛晌、AngularJS 、JQuery 誰(shuí)還都談不上能一統(tǒng)江湖斗幼∨熘耄“武林新貴” Flux 、Redux 蜕窿、Mobx 們已經(jīng)忙著爭(zhēng)奪誰(shuí)是數(shù)據(jù)流框架老大谋逻。Native 端 RN 剛偃旗息鼓,Weex 就大有“ I’m the everywhere ”之勢(shì)桐经。連備受爭(zhēng)議的 GraphQL 內(nèi)部都還有 Apollo毁兆、Relay 掐來(lái)掐去。
常聽(tīng)到身邊的前端工程師抱怨阴挣,上周剛發(fā)布的 XXX 新版本文檔還沒(méi)看气堕,今天 YYY 公司又發(fā)布了新框架,到底先學(xué)哪個(gè)畔咧?其實(shí)茎芭,無(wú)論是哪種框架哪項(xiàng)技術(shù)都是解決實(shí)際業(yè)務(wù)需求的手段、方法誓沸,和武林中各門(mén)各派的武功招式是一樣的梅桩,各有所長(zhǎng),各有各的獨(dú)到之處拜隧。
我們學(xué)習(xí)技術(shù)宿百,除了了解具體使用方法,還需要掌握技術(shù)背后的設(shè)計(jì)理念和工程思想洪添,這些背后的東西是我們技術(shù)選型的依據(jù)垦页,是架構(gòu)設(shè)計(jì)的基礎(chǔ),是軟件系統(tǒng)的靈魂薇组。這就好比是的武功中“內(nèi)功心法”催動(dòng)拳腳刀槍?zhuān)徽幸皇酵獗郏⒒⑸L(fēng)坐儿,縱有大敵當(dāng)前律胀,亦是淡然自若宋光。
接下來(lái)分別談一下三種工程思想,分別是:“開(kāi)閉原則”炭菌、“函數(shù)式編程”和“消息機(jī)制”罪佳,這三種工程思想在后端開(kāi)發(fā)中均有廣泛的使用,容易被大家忽略的是目前很多前端技術(shù)框架也應(yīng)用了這三種思想黑低,以下結(jié)合具體案例分析赘艳,希望能夠幫助大家加深對(duì)技術(shù)本身的理解。
開(kāi)閉原則
說(shuō)到面向?qū)ο笤O(shè)計(jì)克握,大部分人腦海中閃過(guò)的恐怕都是“23種設(shè)計(jì)模式”蕾管。設(shè)計(jì)模式代表的是業(yè)務(wù)場(chǎng)景中總結(jié)出的最佳實(shí)現(xiàn)方式,屬于實(shí)踐的范疇菩暗,在其之上是更為重要的“SOLID”五大原則:
Single Responsibility Principle 單一責(zé)任原則
The Open Closed Principle 開(kāi)放封閉原則
The Liskov Substitution Principle 里氏替換原則
The Dependency Inversion Principle 依賴(lài)倒置原則
The Interface Segregation Principle 接口分離原則
SOLID 五大原則的出發(fā)點(diǎn)也是軟件工程的終極目標(biāo):“高內(nèi)聚掰曾、低耦合”。在后端開(kāi)發(fā)中運(yùn)用最多的是“依賴(lài)倒置原則”停团,與其相關(guān)的設(shè)計(jì)模式大約有5-6個(gè)旷坦。如下圖所示:
<figure class="" style="margin: 0px 0px 16px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; background-color: rgb(255, 255, 255); font-family: "-apple-system Helvetica", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; font-size: 16px; overflow-wrap: break-word !important;"></figure>
上圖也可以理解為從抽象概念到具體實(shí)踐的逐步演進(jìn)。
在前端技術(shù)框架中佑稠,運(yùn)用最多的是“開(kāi)放封閉原則”秒梅,我們先來(lái)看一下這條原則是怎么定義的:
A software artifact should be open for extension but closed for modification.
翻譯過(guò)來(lái)就是:軟件系統(tǒng)應(yīng)當(dāng)對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉(感覺(jué)像沒(méi)說(shuō))舌胶。這里舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明開(kāi)閉原則捆蜀,先幫助大家理解概念:
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;
}
上例中無(wú)論場(chǎng)景如何擴(kuò)展,Area 函數(shù)都無(wú)需修改幔嫂,每個(gè) Shape 類(lèi)通過(guò)繼承接口和多態(tài)特性漱办,各自實(shí)現(xiàn)面積計(jì)算。
總結(jié)一下開(kāi)閉原則就是:軟件系統(tǒng)的核心邏輯都不應(yīng)該輕易改變婉烟,否則會(huì)破壞系統(tǒng)的穩(wěn)定性和增加測(cè)試成本娩井。我們應(yīng)當(dāng)建立合適的抽象并統(tǒng)一接口,當(dāng)業(yè)務(wù)需要擴(kuò)展時(shí)似袁,我們可以通過(guò)增加實(shí)體類(lèi)來(lái)完成洞辣。
接下來(lái)我們看一個(gè)“開(kāi)閉原則”在前端框架中的應(yīng)用: Ant Design 組件庫(kù)中的 Form 表單組件。
和其它組件不同昙衅,F(xiàn)orm 組件并沒(méi)有具體的形態(tài)扬霜,它更像是一個(gè)容器,提供了接入的標(biāo)準(zhǔn)而涉,并提供了校驗(yàn)著瓶、表單提交等功能。繪制表單中的一項(xiàng)如下所示:
<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 組件庫(kù)中已經(jīng)提供個(gè)幾乎所有的常見(jiàn)表單組件啼县,如:Select 材原、Checkbox 沸久、Radio 、Cascader 等余蟹,但在實(shí)際業(yè)務(wù)中卷胯,我們還是會(huì)需要設(shè)計(jì)業(yè)務(wù)相關(guān)的表單項(xiàng),F(xiàn)orm 表單通過(guò)統(tǒng)一組件接口的方式滿足了這個(gè)技術(shù)需求威酒,具體規(guī)約如下:
自定義或第三方的表單控件窑睁,也可以與 Form 組件一起使用。只要該組件遵循以下的約定:
提供受控屬性 value 或其它與 valuePropName 的值同名的屬性葵孤。
提供 onChange 事件或 trigger 的值同名的事件担钮。
不能是函數(shù)式組件。
這正是“開(kāi)閉原則”的一個(gè)典型實(shí)踐案例尤仍,即表單核心邏輯(校驗(yàn)裳朋、提交等)保持不變并封裝在 Form 組件中,自定義表單項(xiàng)只需要滿足上述三條規(guī)約吓著,就能平滑接入到 Form 組件中鲤嫡,和 Ant Design 原生組件契合在一起。
Ant Design 中的 Form 組件通過(guò)這樣一個(gè)簡(jiǎn)潔的設(shè)計(jì)绑莺,完美提供了表單類(lèi)型頁(yè)面的統(tǒng)一解決方案暖眼。
函數(shù)式編程
隨著人工智能、區(qū)塊鏈纺裁、AR诫肠、VR、新零售等業(yè)務(wù)場(chǎng)景的出現(xiàn)欺缘,產(chǎn)品界面交互正在變得越來(lái)越復(fù)雜栋豫,這就對(duì)現(xiàn)代的前端開(kāi)發(fā)者提出了更高的要求。如何快速谚殊、正確丧鸯、高效地開(kāi)發(fā)出高復(fù)雜度頁(yè)面是目前前端技術(shù)最需要解決的問(wèn)題。
函數(shù)式編程(以下簡(jiǎn)稱(chēng) FP )憑借其高復(fù)用性嫩絮、易測(cè)試性和與之帶來(lái)的健壯性和簡(jiǎn)潔開(kāi)始逐漸占據(jù)前端技術(shù)圈丛肢,我們發(fā)現(xiàn)越來(lái)越多的前端框架以 FP 為設(shè)計(jì)核心準(zhǔn)則。
我們先簡(jiǎn)單介紹一下 FP剿干,函數(shù)式編程的特征主要包括以下幾個(gè)方面:
函數(shù)為“一等公民”
模塊化蜂怎、組合
引用透明避免狀態(tài)改變
避免共享狀態(tài)
JS 語(yǔ)言中的函數(shù)不同于 Java ,C/C++ 等語(yǔ)言, 可以被當(dāng)做參數(shù)和返回值進(jìn)行傳遞置尔,因此天生具備“一等公民”特性杠步。“模塊化、組合”幽歼、“引用透明”朵锣、“避免狀態(tài)改變”、“避免共享狀態(tài)”這四個(gè)特征都需要通過(guò)特定代碼模式實(shí)現(xiàn)试躏。先舉兩個(gè)小例子:
找出字符串中率先出現(xiàn)的四個(gè)非數(shù)字字符猪勇?
非 FP 風(fēng)格
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++; }}
FP 風(fēng)格
var words = str.split('').filter(function(x){ return (!x.match(/[1-9]+/))}).slice(0,4);
第二段代碼中使用的 js 數(shù)組方法 filter 和 slice设褐,去掉了 for 循環(huán)颠蕴,代碼更簡(jiǎn)潔流暢。在寫(xiě)具體業(yè)務(wù)代碼的時(shí)候助析,“模塊化犀被、組合”是 FP 最常用的技術(shù),也是最重要的實(shí)現(xiàn)功能的手段外冀。
分別實(shí)現(xiàn)數(shù)組所有元素相加寡键、相乘、相與雪隧?
非 FP 風(fēng)格
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);
FP 風(fēng)格
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 函數(shù)代替了 for 循環(huán),并將數(shù)值計(jì)算部分作為模塊提取出來(lái)脑沿,當(dāng)有新的計(jì)算類(lèi)型時(shí)藕畔,只需要在 ops 對(duì)象中定義計(jì)算過(guò)程。這里就體現(xiàn)了 FP 中“模塊化庄拇、組合”的特性注服。在 FP 風(fēng)格下,我們習(xí)慣將復(fù)雜邏輯切割成一個(gè)個(gè)小模塊措近,通過(guò)組合這些模塊實(shí)現(xiàn)新的業(yè)務(wù)功能溶弟,當(dāng)有新的需求到來(lái)時(shí),我們盡可能地復(fù)用已有模塊達(dá)到目標(biāo)瞭郑。FP 代碼在復(fù)用性方面相比 OOD 有明顯的優(yōu)勢(shì)辜御。
React 中的 FP 思想
React 框架中,當(dāng)用戶操作 UI 或者 API 的返回帶來(lái)了數(shù)據(jù)的改變屈张,React 隨即進(jìn)行 virtual dom diff 計(jì)算得到 dom 的修改指令我抠,對(duì) dom 元素應(yīng)用修改指令便得到最新的 html 界面,如下圖所示:
<figure class="" style="margin: 0px 0px 16px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; background-color: rgb(255, 255, 255); font-family: "-apple-system Helvetica", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; font-size: 16px; overflow-wrap: break-word !important;"></figure>
不難發(fā)現(xiàn)袜茧,React 其實(shí)是應(yīng)用數(shù)據(jù)對(duì)UI的一種映射菜拓,不同的數(shù)據(jù)會(huì)映射出不同樣式的 UI 界面,我們可以得出如下的表達(dá)式:
沒(méi)錯(cuò)笛厦,React 的本質(zhì)其實(shí)是一種函數(shù)纳鼎,并且還是符合 FP 要求的“引用透明”函數(shù)。所謂“引用透明”就是指函數(shù)的輸出僅依賴(lài)函數(shù)參數(shù),不受任何外部環(huán)境影響贱鄙。這樣的函數(shù)可測(cè)試性強(qiáng)劝贸,也非常容易進(jìn)行組合。
在 React 的體系下逗宁,任何組件都可由一個(gè)個(gè)更小的組件構(gòu)成映九,每個(gè)組件都只關(guān)心自己的輸入,他們不斷地接受新的數(shù)據(jù)并輸出對(duì)應(yīng)的新的UI界面瞎颗。React 框架中常用的“高階組件”可以看作引用透明”函數(shù)的組合模式件甥。
在具體業(yè)務(wù)中我們通常還需要權(quán)衡 React 組件的復(fù)用性和開(kāi)發(fā)體驗(yàn),如果組件被拆分的過(guò)于細(xì)哼拔,固然復(fù)用性會(huì)提升引有,但文件數(shù)量會(huì)增加,對(duì)應(yīng)的文檔和溝通成本也會(huì)增加倦逐,這也是 FP 在實(shí)踐過(guò)程中經(jīng)常遭人詬病的點(diǎn)譬正,即復(fù)用性提升后帶來(lái)的額外開(kāi)發(fā)成本。
消息機(jī)制
消息機(jī)制是軟件工程中一個(gè)普遍運(yùn)用的工程思想檬姥≡遥“設(shè)計(jì)模式”中的觀察者模式、Windows 操作系統(tǒng)底層健民、Spring 框架中的 ApplicationListener 模塊抒巢、Objective-C 語(yǔ)言中的函數(shù)調(diào)用、都是通過(guò)消息機(jī)制驅(qū)動(dòng)的荞雏。
使用消息機(jī)制最大的好處在于可以做到業(yè)務(wù)模塊間安全解耦虐秦,模塊間通過(guò)發(fā)送消息的方式進(jìn)行協(xié)作,我們先舉一個(gè)后端開(kāi)發(fā)中的例子凤优,下圖是一個(gè)簡(jiǎn)單的預(yù)定系統(tǒng)的建模圖悦陋,并沒(méi)有使用消息機(jī)制:
<figure class="" style="margin: 0px 0px 16px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; background-color: rgb(255, 255, 255); font-family: "-apple-system Helvetica", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; font-size: 16px; overflow-wrap: break-word !important;"></figure>
在沒(méi)有消息機(jī)制的情況下,用戶模塊需要知道訂單模塊的存在筑辨,并向起進(jìn)行接口調(diào)用俺驶,同理訂單模塊需要向支付模塊進(jìn)行接口調(diào)用。這種設(shè)計(jì)下模塊間是耦合的棍辕。
我們?cè)賮?lái)看一下使用消息機(jī)制的情況:
<figure class="" style="margin: 0px 0px 16px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; background-color: rgb(255, 255, 255); font-family: "-apple-system Helvetica", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; font-size: 16px; overflow-wrap: break-word !important;"></figure>
上圖中暮现,無(wú)論是客戶下訂單、支付還是預(yù)定都是通過(guò)消息的方式傳遞的楚昭,每個(gè)模塊都是向一個(gè)消息處理器起發(fā)消息栖袋,同時(shí)也監(jiān)聽(tīng)消息處理器發(fā)送回來(lái)的消息。在這種模式下抚太,模塊完全不知道其它模塊的存在塘幅,徹底做到了解耦昔案。
在前端業(yè)務(wù)開(kāi)發(fā)中,我們經(jīng)常也會(huì)用到 EventEmitter 庫(kù)來(lái)進(jìn)行消息傳遞电媳。比如頁(yè)面上有兩塊區(qū)域踏揣,一塊用 React 框架渲染,一塊用 D3 渲染的匾乓,當(dāng)兩塊區(qū)域需要數(shù)據(jù)同步時(shí)捞稿,就可以使用消息機(jī)制進(jìn)行通訊,保證頁(yè)面數(shù)據(jù)整體一致拼缝。
如果你的業(yè)務(wù)中有不同生命周期的組件娱局,建議采用消息機(jī)制進(jìn)行管理,不僅消除了耦合珍促,邏輯關(guān)系部分的代碼也集中到了一個(gè)文件中铃辖,內(nèi)聚性得到了提升剩愧。
使用消息機(jī)制的一個(gè)附屬產(chǎn)物就是中間件猪叙,我們可以為消息定制各種中間件,在中間中完成一些通用邏輯仁卷,讓業(yè)務(wù)代碼更精煉穴翩。
說(shuō)到前端框架中消息機(jī)制的運(yùn)用,當(dāng)然首推 Redux 框架锦积,在 Redux 框架中芒帕,任何數(shù)據(jù)交互都需要先轉(zhuǎn)化為一個(gè) action,由 action 去觸發(fā) reducer 和相關(guān)的 middleware 處理 action丰介,改變數(shù)據(jù)背蟆,最終同步到頁(yè)面 UI 上,如下圖所示:
<figure class="" style="margin: 0px 0px 16px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; background-color: rgb(255, 255, 255); font-family: "-apple-system Helvetica", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; font-size: 16px; overflow-wrap: break-word !important;"></figure>
關(guān)于使用 Redux 的種種利弊哮幢,在各大社區(qū)中都有很深入的討論带膀,本文不再贅述。
總結(jié)
“開(kāi)閉原則”橙垢、“函數(shù)式編程”垛叨、“消息機(jī)制”這個(gè)三個(gè)軟件工程中重要的思想方法好比三套內(nèi)功口訣,掌握了他們柜某,才能更深刻地理解技術(shù)框架本身嗽元,發(fā)揮出技術(shù)框架的最大威力。
寫(xiě)到這里突然又想起《天龍八部》中一段:
喬峰眼見(jiàn)旁人退開(kāi)喂击,驀地心念一動(dòng)剂癌,呼的一拳打出,一招“沖陣斬將”翰绊,也正是“太祖長(zhǎng)拳”中的招數(shù)佩谷。這一招姿式既瀟灑大方已極办铡,勁力更是剛中有柔,柔中有剛琳要,武林高手畢生所盼望達(dá)到的拳術(shù)完美之境寡具,竟在這一招中表露無(wú)遺。
一套平平無(wú)奇的“太祖長(zhǎng)拳”在喬峰手中盡能有如此氣象稚补!
多少年以后童叠,每當(dāng)人們聊起金庸,聊起那個(gè)武俠世界课幕,想必都會(huì)津津有味地回味厦坛、談?wù)撈鹁圪t莊中這石破天驚的一拳。