軟件設(shè)計(jì)——SOLID原則(一)

要構(gòu)建一個(gè)高水平的軟件系統(tǒng),必須要考慮系統(tǒng)的穩(wěn)定性,可拓展性,靈活性以及健壯性。為了實(shí)現(xiàn)這樣的設(shè)計(jì),我們必須了解五大原則,也就是SOLID原則。本文作為該系列的第一部分將介紹其中的兩大原則。
▌歷史
SOLID原則由Robert C. Martin提出,開(kāi)始于20世紀(jì)80年代,但是概念還不成熟,基本形態(tài)于2000年左右形成,最終于2004年前后誕生。? ??
▌內(nèi)容
SOLID一詞由五個(gè)原則的英文首字母拼接而成,這個(gè)五個(gè)原則為:
SRP (Single Responsibility Principle): 單一職責(zé)原則
OCP (Open/Closed Principle): 開(kāi)閉原則
LSP (Liskov Substitution Principle): 里氏替換原則
ISP (Interface Segregation Principle): 接口隔離原則
DIP (Dependency Inversion Principle):依賴(lài)反轉(zhuǎn)原則
▌單一職責(zé)原則
▌任何一個(gè)軟件模塊都應(yīng)該只對(duì)某一類(lèi)行為者負(fù)責(zé)。
這里的軟件模塊不僅是指某一個(gè)源代碼文件或是一個(gè)類(lèi),在一些情況下也是指著一組緊密相關(guān)的函數(shù)和數(shù)據(jù)結(jié)構(gòu)?,F(xiàn)在,我們通過(guò)一個(gè)反面例子來(lái)理解這個(gè)原則。
無(wú)人機(jī)軟件開(kāi)發(fā)
?

如圖所見(jiàn),這個(gè)類(lèi)的三個(gè)函數(shù)分別對(duì)應(yīng)三類(lèi)差別很大的行為者,這大大違反了SRP原則。
DetectTarget()由視覺(jué)算法工程師(組)開(kāi)發(fā),負(fù)責(zé)視覺(jué)檢測(cè)相關(guān)功能
ControlVehicle()由控制算法工程師(組)開(kāi)發(fā),負(fù)責(zé)無(wú)人機(jī)的控制
SendZigbeeMsg()由嵌入式工程師(組)開(kāi)發(fā),負(fù)責(zé)無(wú)人機(jī)間的通訊
以上三個(gè)函數(shù)放在同一類(lèi)中,導(dǎo)致了三類(lèi)行為者的行為耦合在一起,容易造成三個(gè)開(kāi)發(fā)者甚至三個(gè)開(kāi)發(fā)組的工作互相依賴(lài)并互相影響的問(wèn)題。
比方說(shuō),DetectTarget()和ControlVehicle()函數(shù)都需要計(jì)算目標(biāo)無(wú)人機(jī)的位置。為了提高代碼的復(fù)用率,我們可能會(huì)添加一個(gè)CalculateTargetPos()函數(shù)。

接下來(lái),視覺(jué)開(kāi)發(fā)組提出一個(gè)新的算法,該算法需要修改CalculateTargtetPos()內(nèi)的邏輯。然后,這個(gè)函數(shù)就被修改了,并且也通過(guò)了測(cè)試。他們覺(jué)得這個(gè)新算法性能很好,現(xiàn)在可以去實(shí)機(jī)測(cè)試了。然而,他們沒(méi)有想到控制算法組那邊也用到了這個(gè)函數(shù),并且他們的控制算法并不需要修改CalculateTargtetPos()函數(shù)。
再之后,他們滿懷信心地帶著設(shè)備去做測(cè)試,而控制算法組完全沒(méi)注意他們算法所依賴(lài)的函數(shù)被修改了。結(jié)果顯然易見(jiàn),控制算法出了故障,發(fā)生了嚴(yán)重的實(shí)驗(yàn)事故,無(wú)人機(jī)從空中墜毀。
讀者不要覺(jué)得這樣的事情不常見(jiàn),類(lèi)似的事情在各領(lǐng)域的軟件開(kāi)發(fā)中都是經(jīng)常發(fā)生的事情。這些問(wèn)題的根源就是因?yàn)閷⒉煌袨檎咚蕾?lài)的代碼強(qiáng)行放在一處。所以,SRP就告訴我們這類(lèi)代碼一定要被分開(kāi)。
▌里氏替換原則
▌如果對(duì)于每個(gè)類(lèi)型是S的對(duì)象o1都存在一個(gè)類(lèi)型為T(mén)的對(duì)象o2,能使操作T類(lèi)型的程序P在用o2替換o1時(shí)行為保持不變,我們就可以將S稱(chēng)為T(mén)類(lèi)型的子類(lèi)型。
為了理解這個(gè)原則,我們還是通過(guò)一個(gè)例子來(lái)說(shuō)明。
正方形/長(zhǎng)方形問(wèn)題
該問(wèn)題是一個(gè)典型的違背里氏替換原則的案例。

該案例中,Sqaure類(lèi)并不是Reatangle類(lèi)的子類(lèi)型。Rectangle類(lèi)可以允許長(zhǎng)寬分別被修改,但是Sqaure類(lèi)要求長(zhǎng)和寬被同時(shí)修改,并且數(shù)值要相等。
LSP不僅能運(yùn)用在類(lèi)的設(shè)計(jì)上用以規(guī)范類(lèi)的繼承,也逐漸演變成了一種更廣泛的,指導(dǎo)接口與其實(shí)現(xiàn)方式的設(shè)計(jì)原則。現(xiàn)在我們?cè)倏匆粋€(gè)LSP運(yùn)用在軟件架構(gòu)中的反面案例。
違反LSP的案例
在無(wú)人機(jī)系統(tǒng)中,我們使用Zigbee通訊來(lái)傳遞數(shù)據(jù)。為了發(fā)送和接收數(shù)據(jù),我們必須先規(guī)定好通訊協(xié)議。假設(shè)我們現(xiàn)在使用了某種協(xié)議,其中關(guān)于無(wú)人機(jī)位置的協(xié)議為以下格式。

并且我們定義了EncodePosition()和DecodePosition()接口用以對(duì)無(wú)人機(jī)位置信息進(jìn)行編/解碼。很明顯,這兩個(gè)函數(shù)內(nèi)部的實(shí)現(xiàn)決定了所有參與無(wú)人機(jī)位置通訊的模塊/硬件都必須使用相同的通訊協(xié)議,也必須用相同的編碼方式來(lái)處理X,Y和Z的數(shù)據(jù)。
然后,項(xiàng)目需要通過(guò)地面站來(lái)發(fā)送無(wú)人機(jī)所要到達(dá)的目的地位置。但是,負(fù)責(zé)開(kāi)發(fā)這個(gè)功能的開(kāi)發(fā)者并沒(méi)有好好看通訊協(xié)議手冊(cè),將X和Y的位置弄反了。因?yàn)橹T多原因,地面站的代碼無(wú)法重構(gòu)。所以,架構(gòu)師必須為無(wú)人機(jī)系統(tǒng)添加特殊用例,用以應(yīng)對(duì)這個(gè)“新”的通訊協(xié)議。
當(dāng)然,最簡(jiǎn)單的做法就是添加一條if語(yǔ)句:
if(zigbee.GetBytes().GetSourceID() == OxF2) // OxF2為地面站的SourceID
然而,在代碼中留下“0xF2”會(huì)給后續(xù)帶來(lái)一系列潛在的意想不到的問(wèn)題,甚至?xí)?dǎo)致嚴(yán)重的安全問(wèn)題。
為了解決這個(gè)問(wèn)題,我們需要在系統(tǒng)中添加一個(gè)“編解碼表”,并且將其保存在其他地方,可能是數(shù)據(jù)庫(kù),也可能是本地文件。

然后我們需要增加一個(gè)復(fù)雜的組件來(lái)應(yīng)對(duì)這個(gè)不能完全互相替換的通訊協(xié)議。
所以,在軟件架構(gòu)層面,一旦可替換性被破壞了,該系統(tǒng)架構(gòu)就必須為此增加大量復(fù)雜的應(yīng)對(duì)機(jī)制。
▌結(jié)語(yǔ)
本文介紹了SOLID原則中的兩大原則——單一職責(zé)原則與里氏替換原則。單一職責(zé)原則告訴我們不要將不同的行為者所依賴(lài)的代碼強(qiáng)行放在一起,而里氏替換原則則提醒我們一定要確保類(lèi)和接口間具有可替換性。下一篇文章將介紹其余三種原則,敬請(qǐng)期待。
本文共1781字
由西湖大學(xué)智能無(wú)人系統(tǒng)實(shí)驗(yàn)室工程師陳華奔原創(chuàng)
申請(qǐng)文章授權(quán)請(qǐng)聯(lián)系后臺(tái)相關(guān)運(yùn)營(yíng)人員
▌公眾號(hào):空中機(jī)器人前沿
▌知乎:空中機(jī)器人前沿(本文鏈接:https://zhuanlan.zhihu.com/p/596209925)
▌Youtube:Aerial robotics @ Westlake University
▌實(shí)驗(yàn)室網(wǎng)站:https://shiyuzhao.westlake.edu.cn