摘要:題記真的猛士,敢于不做設(shè)計(jì),直接開始編碼面對(duì)業(yè)務(wù)系統(tǒng)中最復(fù)雜的部分狀態(tài)模型,有多少程序員就有多少種實(shí)現(xiàn)。聊聊狀態(tài)模型設(shè)計(jì)上常遇到的問題和解決的思路吧。
題記
真的猛士,敢于不做設(shè)計(jì),直接開始編碼——面對(duì)業(yè)務(wù)系統(tǒng)中最復(fù)雜的部分:狀態(tài)模型,有多少程序員就有多少種實(shí)現(xiàn)。聊聊狀態(tài)模型設(shè)計(jì)上常遇到的問題和解決的思路吧。
正文有時(shí)候我們想做一個(gè)富含業(yè)務(wù)行為,而又足夠通用的技術(shù)架構(gòu)時(shí),剛開始都是信心滿滿,采用各種設(shè)計(jì)方法,充分考慮未來的需求,畫出系統(tǒng)依賴、數(shù)據(jù)模型甚至核心類圖,上線時(shí)各種性能爆表或者擴(kuò)展輕松;上線半年之后畫風(fēng)一轉(zhuǎn),代碼堆得到處都是,哪怕再小心維護(hù)依然無法逃離“一年一重構(gòu)”的魔咒,兩年過后連測(cè)試同學(xué)的TC都寫不出來,發(fā)生了什么?
寫到這里又到晚上了,不湊巧零食都被清理干凈,餓得天昏地暗(⊙o⊙)…就設(shè)想一下這樣一個(gè)場(chǎng)景吧(下面的討論只做場(chǎng)景討論,并非真實(shí)業(yè)務(wù)系統(tǒng)的設(shè)計(jì),請(qǐng)專業(yè)的同行們不要見怪。
一分鐘速成方案接到一個(gè)炒菜機(jī)器人的項(xiàng)目,要求能夠按照吃貨的設(shè)計(jì)做出各種菜式
大家應(yīng)該比較熟悉需求或者領(lǐng)域驅(qū)動(dòng)的套路吧,抄起自上而下設(shè)計(jì)的錘子開始敲釘子:首先,我們的平臺(tái)中會(huì)有
廚具:各種廚具的基本使用接口和參數(shù)規(guī)范
菜譜:操作指導(dǎo)
先在腦海中預(yù)演一下這樣的設(shè)計(jì)是怎么運(yùn)作的:
首先系統(tǒng)應(yīng)該能夠認(rèn)識(shí)各種不同的廚具,并且知道如何操作它們
案板:切菜程序
炒鍋:翻炒程序、油炸程序
燉鍋:水煮程序、焯水程序
攪拌機(jī):攪拌程序
……
以及它們的清洗程序沒有一一列出
接下來是菜譜,就先來個(gè)番茄炒蛋吧
打蛋流程:使用攪拌機(jī),調(diào)整參數(shù)使其能夠打出均勻的蛋液
番茄流程:使用案板,調(diào)整參數(shù)使其能夠切出合適的番茄塊
炒鍋流程:使用炒鍋,先放油,燒熱,放蛋,翻炒,放番茄,翻炒,加鹽
出鍋流程:使用盤子,出鍋
畢竟我們花了1分鐘設(shè)計(jì)出來的廚具+菜譜架構(gòu),看看再做幾個(gè)需求會(huì)變成什么樣子,需求方要求在能做番茄炒蛋的基礎(chǔ)上,做個(gè)辣椒絲炒蛋:
打蛋流程:使用攪拌機(jī),調(diào)整參數(shù)使其能夠打出均勻的蛋液
辣椒流程:使用案板,調(diào)整參數(shù)使其能夠切出合適的辣椒絲
炒鍋流程:使用炒鍋,先放油,燒熱,放蛋,翻炒,放辣椒,翻炒,加鹽
出鍋流程:使用盤子,出鍋
做到這里,一些同學(xué)指出,多數(shù)雞蛋搭配的菜譜都是擁有四個(gè)標(biāo)準(zhǔn)流程節(jié)點(diǎn):打蛋、切菜、翻炒、裝盤,而在切菜環(huán)節(jié)中,我們只需要調(diào)整參數(shù)類型和數(shù)值,就可以搭配出“*炒雞蛋”的菜色,至于翻炒環(huán)節(jié)相對(duì)麻煩一些,需要加入很多細(xì)粒度的操作才能做出適用于業(yè)務(wù)發(fā)展的擴(kuò)展性來;接下來的工作重點(diǎn),要放在翻炒流程的設(shè)計(jì)上,開放出盡可能多的SPI,讓第三方在我們這個(gè)平臺(tái)上共同實(shí)現(xiàn)翻炒市場(chǎng)。
系統(tǒng)上線半年,出現(xiàn)了各種業(yè)務(wù)分支:不僅原先官方提供的的番茄炒蛋和辣椒炒蛋獲得很好的市場(chǎng)反饋,微調(diào)參數(shù)就輕松支持了苦瓜炒蛋、木耳炒蛋甚至榴蓮炒蛋;業(yè)務(wù)方出現(xiàn)了:我們要開辟湯類市場(chǎng),先從番茄蛋湯開始吧:
打蛋流程:使用攪拌機(jī),調(diào)整參數(shù)使其能夠打出均勻的蛋液
切菜流程:使用案板,調(diào)整參數(shù)切出番茄丁和香蔥段
湯鍋流程:使用湯鍋,加水,燒熱,放蛋,加熱,放番茄,放蔥段,加鹽
出鍋流程:使用湯碗,出鍋
針對(duì)原來設(shè)計(jì)的四套流程,在切菜流程中加入了類似炒鍋流程的多操作支持,接下來又實(shí)現(xiàn)了一套全新的湯鍋流程,出鍋也做了些定制。
腦補(bǔ)一下接下來的紅燒肘子、魚香肉絲、清燉羊肉、回鍋肉該怎么實(shí)現(xiàn)吧(唉,快餓死了,話說好多程序員做飯都是好手,是真的吧?)
回過頭看看之前的實(shí)現(xiàn),核心流程節(jié)點(diǎn)不一定只有4個(gè),每個(gè)流程節(jié)點(diǎn)下面的子節(jié)點(diǎn)可能有多個(gè),如果現(xiàn)在要針對(duì)業(yè)務(wù)方提出的這些葷菜做個(gè)重構(gòu),該怎么做?
將菜譜系統(tǒng)做成一個(gè)多維數(shù)組,就像這個(gè)樣子:
菜譜ID[子流程ID],然后分別實(shí)現(xiàn)這些流程節(jié)點(diǎn)并將它們存儲(chǔ)在這個(gè)菜譜表格中
看起來應(yīng)該比較完善了吧,可程序員的第六感還是隱隱約約覺得有哪里不對(duì)勁,譬如,在番茄炒蛋和辣椒絲炒蛋這兩個(gè)大體相同的流程中,“翻炒”、“加鹽”這兩個(gè)節(jié)點(diǎn)真的是可復(fù)用的嗎?
沒錯(cuò),現(xiàn)實(shí)架構(gòu)中往往沒這么簡(jiǎn)單,因?yàn)?/p>
番茄炒蛋的湯多,鹽可以在最后加,也可以在打蛋的時(shí)候加,而辣椒炒蛋沒什么湯,鹽要在打蛋的時(shí)候加進(jìn)去
好吧,在不影響流程系統(tǒng)的情況下,我們硬著頭皮在打蛋和炒鍋流程節(jié)點(diǎn)上加了個(gè)IF判斷(is 番茄炒蛋),然后就……中招了。
從邏輯來講,這一個(gè)小小的IF,將我們?cè)仍O(shè)計(jì)的三維數(shù)組變了個(gè)味,把流程圖畫出來可能是這樣的:
IF...ELSE分支就像小說里邊的二向箔,看起來像是沒有改變?cè)邢到y(tǒng)的邏輯,但可是可但是它可是混雜在源碼中而不是存在于配置中的邏輯,慢慢的,這種隨意的維護(hù)和簡(jiǎn)單實(shí)現(xiàn),開始模糊系統(tǒng)中的主子流程的邊界,接著模糊菜譜和主流程的邊界,將一個(gè)有層次的設(shè)計(jì)一步一步的煮成一鍋皮蛋瘦肉東北亂燉粥。寫代碼的時(shí)候很爽,做維護(hù)的時(shí)候罵娘
這是一個(gè)小小的開始,我們可以抱著取舍的心態(tài)說:我們可以通過編碼規(guī)范的方式要求開發(fā)人員在涉及主流程節(jié)點(diǎn)的邏輯上不允許使用IF分支來保護(hù)架構(gòu),只有細(xì)枝末節(jié)的流程可以使用不規(guī)范的編碼方式。
可現(xiàn)實(shí)往往沒有那么簡(jiǎn)單,流程節(jié)點(diǎn)之間也不是完全沒有上下文依賴的情況,絕大多數(shù)采用狀態(tài)機(jī)架構(gòu)的系統(tǒng)是不會(huì)用多維數(shù)組劃分狀態(tài)的(不信你可以去review代碼),通過邏輯分支搭建的橋梁,整個(gè)系統(tǒng)變成一個(gè)巨大的狀態(tài)機(jī),那么一個(gè)高維的狀態(tài)機(jī)系統(tǒng)投影到單維的系統(tǒng)中會(huì)發(fā)生什么?(很多視頻網(wǎng)站上有個(gè)很好的教學(xué)系列《Dimensions》,有一部分內(nèi)容關(guān)于如何通過球極投影理解四維空間)狀態(tài)機(jī)爆炸了,囧
這種實(shí)現(xiàn)的問題還不止于此,由于細(xì)粒度的流程是建立在廚具的維度上,而每種廚具對(duì)付不同的食材時(shí),還是需要做很多定制化的工作,譬如:
打蛋器/打蛋碗是否能夠加鹽
鍋里倒油/倒水/倒醬油
菜板切圈/切片/切絲
就拿菜板舉例,胡蘿卜切片的手法,和包心菜切片的手法必然是不同的;習(xí)慣上對(duì)待這種問題的解決手段通常有兩種,一種是把切片的代碼放在菜板上實(shí)現(xiàn),另一種是將復(fù)雜性下沉到各種食材上分別實(shí)現(xiàn)
如果在菜板上實(shí)現(xiàn),我們就將獲得一個(gè)能夠加工天下食材的“超級(jí)菜板”,要么是個(gè)上帝類,要么是個(gè)錯(cuò)綜復(fù)雜的巨型Service;
如果在食材上實(shí)現(xiàn),為了能讓菜板接受各種不同類型食材作為輸入?yún)?shù),我們很可能會(huì)在各種食材的上層抽象一個(gè)BaseEdible的基類好傳遞參數(shù),然后要么在菜板上做switch邏輯,要么在BaseEdible中提供各種加工方法的實(shí)現(xiàn),譬如切片/切絲,但粉條或者大米怎么切片?它和胡蘿卜除了都能吃以外還有什么共性?
也許有細(xì)心的同學(xué)開始考慮在食材上用Command方式來實(shí)現(xiàn)行為,這可能也是一種很糾結(jié)的做法,Debug成本暫且不說,一個(gè)幾乎可以發(fā)送所有命令的菜板加上一堆看起來什么命令都能接受的食材實(shí)現(xiàn),怎么保證系統(tǒng)不會(huì)讓菜板去把面粉切個(gè)絲,也要做不少工作。
一個(gè)看上去很美好的設(shè)計(jì),在實(shí)施的過程中很可能成為下面三者兼?zhèn)涞脑愀鈱?shí)現(xiàn)
狀態(tài)機(jī)爆炸
上帝類
過度繼承/無用代碼
這時(shí)候,比程序員先瘋掉的,大概是聽到程序員說“我做了個(gè)小改動(dòng),你們回歸一下”這句話的測(cè)試同學(xué)吧?
換個(gè)姿勢(shì)怎樣才能讓這個(gè)系統(tǒng)像親愛的母上大人一樣,什么菜都會(huì)做呢?
回顧前面的設(shè)計(jì),鍋碗瓢盆作為容器,它們本身其實(shí)沒有發(fā)生過任何變化,只是在盛有不同的食材時(shí),樣子看起來有些不同。按照加工的流程設(shè)計(jì)狀態(tài)機(jī)踩了坑,按照容器狀態(tài)設(shè)計(jì)狀態(tài)機(jī)子節(jié)點(diǎn)也踩了坑,那么我們是不是一開始的出發(fā)點(diǎn)就跑偏了?
我們不妨換個(gè)角度來思考,《舌尖上的中國(guó)》教育我們:食材很重要,那么是不是可以從食材入手來設(shè)計(jì)這個(gè)系統(tǒng)?
剛才的設(shè)計(jì)都是以廚師的工作狀態(tài)為出發(fā)點(diǎn)做的:我們手上有各種工具,可以通過各種手段來加工食材。但考慮“做菜”這件事情本身,輸入的是食材,輸出的也是食材,真正發(fā)生狀態(tài)轉(zhuǎn)換的元素是工具嗎?顯然不是,雞蛋到蛋液到炒蛋這個(gè)過程中,鍋碗瓢盆沒有任何變化,從食材的角度入手,也是一個(gè)好玩的嘗試。
雞蛋蛋有很多加工方法,煮蛋,煎蛋,荷包蛋,蛋液
辣椒也有很多加工方法,辣椒絲,辣椒圈,辣椒片
根據(jù)每一種食材進(jìn)行抽象,會(huì)有一個(gè)很好的附帶效果:每個(gè)狀態(tài)之間的變遷不可逆而且轉(zhuǎn)移條件沒有多態(tài)行為
碗 -> 洗干凈 -> 干凈的碗
雞蛋 -> 敲開,放入干凈的碗并用筷子攪動(dòng) -> 蛋液
雞蛋 -> 敲開,放入干凈的碗并用筷子攪動(dòng),加鹽,繼續(xù)攪動(dòng) -> 咸蛋液
鍋 -> 洗干凈 -> 干凈的鍋
干凈的鍋 -> 放油,加熱 -> 熱油鍋
蛋液 -> 放入熱油鍋,翻炒 -> 炒雞蛋
番茄 -> 切碎 -> 碎番茄
辣椒 -> 切絲 -> 辣椒絲
辣椒 -> 切圈 -> 辣椒圈
荷包蛋很難變回生雞蛋的樣子,對(duì)吧?話說還真有人能做到,不過即使能變回來也不影響整體的設(shè)計(jì)
這樣就有了材料的狀態(tài)機(jī)和轉(zhuǎn)移函數(shù),之前的架構(gòu)中,加工工具變成了Services或者Utils,鍋具仍然維持細(xì)粒度狀態(tài)機(jī),但不再是接受各種食材的上帝組件,我們可以在這樣細(xì)粒度的狀態(tài)機(jī)模型上進(jìn)行很細(xì)致的加工
在不同菜色的加工過程中,這些細(xì)粒度的狀態(tài)機(jī)節(jié)點(diǎn)和轉(zhuǎn)移函數(shù)其實(shí)都是可以完整復(fù)用的,列一個(gè)番茄炒蛋的上游流程圖,是不是變得清晰一些?
剩下的部分就不劇透了,設(shè)計(jì)的樂趣不就在這里嗎?真的餓到全身無力扯不動(dòng)蛋啦!有機(jī)會(huì)大家直接討論下,因?yàn)轭愃频膯栴}在交易、物流、工單系統(tǒng)中都有大量的實(shí)例,偶爾換個(gè)思路,收獲沒準(zhǔn)不小滴。
炒蛋·交易核心實(shí)戰(zhàn)在進(jìn)入這個(gè)章節(jié)之前,我們?cè)倩仡櫼幌?,大家是否已?jīng)通過腦補(bǔ),消滅了炒蛋系統(tǒng)中的那些問題設(shè)計(jì)?
上帝類
過度繼承
非原子的狀態(tài)機(jī)轉(zhuǎn)移函數(shù)
狀態(tài)機(jī)爆炸
如果大體上沒什么問題的話,咱們繼續(xù)向交易核心系統(tǒng)的設(shè)計(jì)上折騰起來!
發(fā)現(xiàn)了嗎,炒蛋和交易核心系統(tǒng)的架構(gòu)也有許多相似之處,復(fù)雜業(yè)務(wù)系統(tǒng)編碼中,最困難的工作是:知道在什么情況下做什么事;我們從炒蛋的思路入手,以兩個(gè)交易場(chǎng)景看看能不能跑通
按照前文的套路,交易系統(tǒng)的設(shè)計(jì)同樣也可以例舉出兩種典型的設(shè)計(jì)路徑:
1、按照資金流、信息流、物流等人們直接感知到的交易元素,自上而下的設(shè)計(jì)
2、按照商品、資金、優(yōu)惠券、紅包等交易物料,自下而上的設(shè)計(jì)
很多同學(xué)對(duì)于前者的思路應(yīng)該比較熟悉了,我們?cè)谶@里重點(diǎn)看一看怎樣以后者的路徑進(jìn)行交易核心的設(shè)計(jì),是否能夠找到一種可以適用于更多業(yè)務(wù)場(chǎng)景、健壯的交易架構(gòu)
首先需要明確,交易的本質(zhì)是什么:多個(gè)參與者按照約定,進(jìn)行財(cái)物的轉(zhuǎn)移或在參與者之間發(fā)生服務(wù)行為
接下來就是如何在系統(tǒng)中體現(xiàn)這些轉(zhuǎn)移或者服務(wù)行為了,會(huì)計(jì)記賬法,在資產(chǎn)核算、資金審計(jì)等領(lǐng)域都有廣泛的應(yīng)用,在我們的交易系統(tǒng)設(shè)計(jì)中,參照會(huì)計(jì)手段,對(duì)每個(gè)交易元素的狀態(tài)進(jìn)行建模(主要體現(xiàn)在數(shù)據(jù)庫(kù)Schema上,本文篇幅所限就不展開介紹會(huì)計(jì)記賬在訂單存儲(chǔ)上的應(yīng)用了,以后有機(jī)會(huì)再敘)
多帶帶抽出狀態(tài)機(jī)來看,用會(huì)計(jì)術(shù)語描述,可以把財(cái)物轉(zhuǎn)移或服務(wù)行為抽象成一個(gè)簡(jiǎn)單的流程
[A]簽訂
[D]借方已履行
[C]貸方已確認(rèn)
對(duì)于任何一種交易元素,無論是否采用了擔(dān)保交易或第三方介入服務(wù)的情況(如對(duì)于商品而言,從賣家發(fā)貨,經(jīng)過快遞,到達(dá)買家的過程中:發(fā)貨及物流進(jìn)行中的狀態(tài)為D,買家確認(rèn)收貨為C)當(dāng)然,完全可以引入更多復(fù)雜的細(xì)粒度狀態(tài)機(jī)。為了敘事簡(jiǎn)便,在后面的表格中,我們都用A、D、C三種狀態(tài)來描述交易元素的轉(zhuǎn)移狀態(tài)。
在一個(gè)典型的一口價(jià)交易流程中,交易流程如下(默認(rèn)全部都是擔(dān)保交易)
下單
買家已付款
賣家已發(fā)貨
買家確認(rèn)收貨
交易成功
在最樸素的一口價(jià)交易流程中,就是買家賣家一手交錢一手交貨的過程,交易元素只有兩個(gè)
資金
商品
那么,在交易過程中,這兩種交易元素的狀態(tài)是如何變更的?
類型 | 下單 | 付款 | 發(fā)貨 | 收貨 | 成功 |
---|---|---|---|---|---|
資金 | A | D | D | D | C |
商品 | A | A | D | C | C |
表格中,不難看出交易的各個(gè)環(huán)節(jié)中需要進(jìn)行的操作
下單(錢A->D,引導(dǎo)買家付款)
買家已付款(貨A->D,引導(dǎo)賣家發(fā)貨)
賣家已發(fā)貨(貨D->C,引導(dǎo)買家確認(rèn)收貨)
買家確認(rèn)收貨(錢D->C,系統(tǒng)打款給賣家)
交易成功
就這樣,從最細(xì)粒度的狀態(tài)機(jī)入手,我們獲得了一個(gè)能夠直接明確表示每個(gè)State和Transition的設(shè)計(jì)原型
下面我們?cè)僬乙粋€(gè)更復(fù)雜一點(diǎn)的場(chǎng)景入手
某烘焙供應(yīng)商接入在線交易,由其分銷商以代理的方式引導(dǎo)用戶選購(gòu),用戶在分銷商頁面在線選購(gòu)商品后,付定金,均分給分銷商及供應(yīng)商,等店鋪備貨完成之后,通知買家付尾款給供應(yīng)商,然后供應(yīng)商發(fā)貨,買家收貨,完成交易
首先列出交易流程列表
下單
買家付定金,供應(yīng)商與分銷商均分
供應(yīng)商完成備貨
買家付尾款給供應(yīng)商
供應(yīng)商發(fā)貨
買家確認(rèn)收貨
交易成功
再列出交易元素
分銷商傭金
供應(yīng)商定金
供應(yīng)商尾款
供應(yīng)商備貨
供應(yīng)商商品
列出交易流程狀態(tài)表格
類型 | 下單 | 定金 | 備貨 | 尾款 | 發(fā)貨 | 收貨 | 成功 |
---|---|---|---|---|---|---|---|
傭金 | A | D | D | D | D | D | C |
定金 | A | D | D | D | D | D | C |
備貨 | A | A | C | C | C | C | C |
尾款 | A | A | A | D | D | D | C |
商品 | A | A | A | A | A | D | C |
當(dāng)然也可以簡(jiǎn)化一下
類型 | 下單 | 定金 | 備貨 | 尾款 | 發(fā)貨 | 收貨 | 成功 |
---|---|---|---|---|---|---|---|
傭金 | A | D | - | - | - | - | C |
定金 | A | D | - | - | - | - | C |
備貨 | A | - | DC | - | - | - | - |
尾款 | A | - | - | D | - | - | C |
商品 | A | - | - | - | - | D | C |
如果需要支持使用優(yōu)惠券的交易呢?再加一行就行
類型 | 下單 | 定金 | 備貨 | 尾款 | 發(fā)貨 | 收貨 | 成功 |
---|---|---|---|---|---|---|---|
券(付款減) | A | D | - | - | - | - | C |
券(下單減) | D | - | - | - | - | - | C |
用這樣的細(xì)粒度表格,很輕松就可以獲得每個(gè)State(下單、付定金),以及State之間的Transitions,表格中,不同的列表示交易環(huán)節(jié),而不同的行,則表示不同的交易元素
至于逆向流程的支持,其實(shí)也很簡(jiǎn)單,因?yàn)楸砀裰幸呀?jīng)清晰的描述了每種交易元素的狀態(tài),將交易元素的發(fā)起人和接收人互換,進(jìn)一步區(qū)分交易元素
需要區(qū)分的交易元素主要有
平臺(tái)中轉(zhuǎn)或擔(dān)保類元素(如現(xiàn)金、紅包、優(yōu)惠券)
不可退換元素(優(yōu)惠券、充值卡)
實(shí)物(通常所說)
服務(wù)
按照交易元素的逆向特征來設(shè)計(jì)對(duì)應(yīng)的逆向元素生成策略,就可以不用考慮太多細(xì)節(jié),簡(jiǎn)便的支持逆向流程
至此,我們獲得了一個(gè)通過細(xì)粒度狀態(tài)機(jī)表示的交易核心模塊,大家也可以再用其他的交易場(chǎng)景試著套用一下,看看有沒有比較好或者不適合的場(chǎng)景
在線上應(yīng)用的設(shè)計(jì)上,還要進(jìn)一步考慮一些其他的工程因素
如何進(jìn)行狀態(tài)機(jī)編碼
如何借助TCC實(shí)現(xiàn)最終事務(wù)一致性
合約化的交易數(shù)據(jù)庫(kù)Schema設(shè)計(jì)
這些內(nèi)容我們?cè)谝院蟮钠新接懓?:)
小結(jié)在很多復(fù)雜業(yè)務(wù)系統(tǒng)的設(shè)計(jì)中,往往因?yàn)榻=嵌鹊倪x取造成后續(xù)維護(hù)中的困難
在狀態(tài)機(jī)/流程引擎的設(shè)計(jì)上,建議考慮
節(jié)點(diǎn)之間的轉(zhuǎn)移函數(shù)是否多態(tài)?
節(jié)點(diǎn)本身是否多態(tài)?
節(jié)點(diǎn)是否清晰的映射了需求場(chǎng)景中那些真正發(fā)生改變的對(duì)象?
新增流程是否需要修改原有代碼?
在類層次設(shè)計(jì)上,建議考慮
是否存在無用代碼?
是否存在上帝類?
如果存在這些情況,就像蛋糕上的霉斑,看起來只有一星半點(diǎn),但你敢吃長(zhǎng)霉的食物嗎?
附錄 開閉原則還記得“開閉原則”嗎,就一句話:系統(tǒng)(或者理解為系統(tǒng)中的類、模塊、函數(shù))對(duì)于“擴(kuò)展”應(yīng)該開放,而對(duì)于“修改”應(yīng)該是封閉的。
這里首先需要界定“擴(kuò)展”的含義:在不改動(dòng)原有系統(tǒng)代碼的情況下,新增一個(gè)類算不算?新增一個(gè)方法呢?如果將它們套用“修改”的語義,對(duì)于模塊而言新增一個(gè)類是修改,對(duì)于類而言新增方法也是修改。開閉原則的邊界似乎沒有那么清晰。
我們可以用一個(gè)更簡(jiǎn)單的思路來界定開閉原則:是否違背了原有的設(shè)計(jì)初衷
繼承的問題《重構(gòu)》書中花了很大的篇幅向讀者介紹“代碼的壞味道”,有一個(gè)“Unnecessary Code”的說法,大致的意思是繼承體系的基類中存在下游子類不需要的行為,或者必須被糾正的情況,工作個(gè)三五年的朋友們都或多或少的遇到過某個(gè)類的所有子類都在復(fù)寫基類方法的情況吧。
在不少實(shí)用主義的架構(gòu)文章中,都提過“使用組合來代替繼承”的觀點(diǎn),其中流傳最廣的一個(gè)段子(抱歉我也不知道是不是真事)是:James Gosling 的某次演講會(huì)后Q&A環(huán)節(jié)中,有人問他,如果重新設(shè)計(jì)Java語言,你會(huì)做什么?JG回答說,我會(huì)干掉“類”,倒不是因?yàn)椤邦悺北旧碛袉栴},而是會(huì)用實(shí)現(xiàn)(implements)來取代繼承(extends)
就平時(shí)項(xiàng)目的經(jīng)歷而言,基類,尤其是業(yè)務(wù)系統(tǒng)中的各種基類,是非常難設(shè)計(jì)的。因?yàn)楹茈y在業(yè)務(wù)剛開始的時(shí)候就預(yù)想到它最終(結(jié)束維護(hù)下線時(shí))的樣子,也常常因?yàn)檫@樣,我們看到的大多數(shù)Base*命名的類時(shí),除了限定參數(shù)類型,它的代碼行為和Object基本無異(譬如BaseCommand,BaseItem,BaseAction我去太多了),讓后續(xù)維護(hù)的同事邊罵邊寫代碼。
上帝類又是來自《重構(gòu)》的點(diǎn)子:“One Class to rule them all, and in the darkness bind them.”,聽起來有點(diǎn)像《魔戒》的臺(tái)詞哈,這個(gè)理解起來就不像為什么避免繼承那么糾結(jié)了,畢竟誰都不愿意維護(hù)一段三五千行而且看起來什么事情都能做的代碼吧?哦,有人用一個(gè)匯編文件寫了個(gè)操作系統(tǒng),咱們的平臺(tái)看起來最多需要兩三個(gè)類就夠了。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/64817.html
摘要:前端日?qǐng)?bào)精選京東如何配合業(yè)務(wù)打造三端融合開發(fā)平臺(tái)第期聊聊組件開發(fā)的邊界把握和狀態(tài)驅(qū)動(dòng)插件拾趣網(wǎng)站開發(fā)微信小程序?qū)崙?zhàn)二全家桶開發(fā)的一個(gè)跨三端的應(yīng)用掘金原生實(shí)現(xiàn)五子棋游戲前端學(xué)習(xí)中文譯年你應(yīng)該了解的函數(shù)式編程個(gè)人文章的設(shè) 2017-06-19 前端日?qǐng)?bào) 精選 京東618:如何配合業(yè)務(wù)打造JDReact三端融合開發(fā)平臺(tái)?【第970期】聊聊vue組件開發(fā)的邊界把握和狀態(tài)驅(qū)動(dòng)webpack 插件...
摘要:隨著世界各國(guó)對(duì)云計(jì)算的大力投入,以及行業(yè)內(nèi)技術(shù)的快速發(fā)展,現(xiàn)階段整個(gè)產(chǎn)業(yè)公司均在進(jìn)行云計(jì)算的產(chǎn)業(yè)整合。對(duì)于云計(jì)算的發(fā)展前景,各大互聯(lián)網(wǎng)巨頭都相當(dāng)看好,紛紛為此調(diào)整公司的發(fā)展戰(zhàn)略,競(jìng)相推出自己的云計(jì)算產(chǎn)品和服務(wù)。隨著世界各國(guó)對(duì)云計(jì)算的大力投入,以及IT行業(yè)內(nèi)技術(shù)的快速發(fā)展,現(xiàn)階段整個(gè)IT產(chǎn)業(yè)公司均在進(jìn)行云計(jì)算的產(chǎn)業(yè)整合。對(duì)于云計(jì)算的發(fā)展前景,各大互聯(lián)網(wǎng)巨頭都相當(dāng)看好,紛紛為此調(diào)整公司的發(fā)展戰(zhàn)略...
摘要:和區(qū)別,前者發(fā)音時(shí),舌頭位置不但不下拉,還要向上顎貼近,那么發(fā)出來音正確后者舌尖向下拉,發(fā)音正確,否則不對(duì)。 i:和I區(qū)別,前者發(fā)音時(shí),舌頭位置不但不下拉,還要向上顎貼近,那么發(fā)出來音正確;后者舌尖向下拉,發(fā)音正確,否則不對(duì)。
摘要:前端與狀態(tài)現(xiàn)在的前端開發(fā)中,對(duì)于狀態(tài)的管理是重中之重。有限狀態(tài)機(jī)那么如何更好的管理前端軟件的復(fù)雜度的狀態(tài)機(jī)思想給出了自己的答案。有限狀態(tài)機(jī)并不是一個(gè)復(fù)雜的概念簡(jiǎn)單說,它有三個(gè)特征狀態(tài)總數(shù)是有限的。 前提 在現(xiàn)在的前端社區(qū),關(guān)于MVVM、Model driven view 之類的概念,已經(jīng)算是非常普及了。React/Vue 這類框架可以算是代表。而自己雖然有 React/Vue 的使用經(jīng)...
摘要:狀態(tài)機(jī)模型區(qū)塊鏈用許多的節(jié)點(diǎn)共同模擬了一臺(tái)多復(fù)本的狀態(tài)機(jī)。區(qū)塊鏈共識(shí)的四個(gè)階段第一階段是加入共識(shí)加入共識(shí)階段決定了什么樣的節(jié)點(diǎn)可以參與共識(shí)協(xié)議。第四階段是退出共識(shí)這是常常被忽略的部分。 在接下來的秘猿科技小課堂里,我們會(huì)從技術(shù)角度、經(jīng)濟(jì)模型設(shè)計(jì)角度、以及共識(shí)角度來拆解 Nervos 加密經(jīng)濟(jì)網(wǎng)絡(luò)中,底層公鏈 CKB 的設(shè)計(jì)理念。而本文將會(huì)作為技術(shù)角度核心設(shè)計(jì) Cell 模型的預(yù)備文章,...
閱讀 2516·2021-11-15 11:36
閱讀 1260·2019-08-30 15:56
閱讀 2313·2019-08-30 15:53
閱讀 1104·2019-08-30 15:44
閱讀 715·2019-08-30 14:13
閱讀 1052·2019-08-30 10:58
閱讀 542·2019-08-29 15:35
閱讀 1370·2019-08-29 13:58