五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

第 56 講:類(lèi)型良構(gòu)規(guī)范(六):SOLID 原則

2021-08-17 06:35 作者:SunnieShine  | 我要投稿

學(xué)完了基本三個(gè)接口的使用后,我們要學(xué)一點(diǎn)輕松但重要的良構(gòu)規(guī)范:SOLID 原則。SOLID 是下面這五個(gè)規(guī)范的名字的首字母縮寫(xiě)拼湊起來(lái)的單詞:

  • 單一職責(zé)原則Single Responsibility Principle,簡(jiǎn)稱(chēng) SRP);

  • 開(kāi)閉原則Open/Closed Principle,簡(jiǎn)稱(chēng) OCP);

  • 李斯柯夫替換原則(也叫里氏替換原則,Liskov Substitution Principle,簡(jiǎn)稱(chēng) LSP);

  • 接口隔離原則Interface Segregation Principle,簡(jiǎn)稱(chēng) ISP);

  • 依賴(lài)反轉(zhuǎn)原則(也叫依賴(lài)倒置原則Dependency Inversion Principle,簡(jiǎn)稱(chēng) DIP)。

如果學(xué)會(huì)了這五大編程規(guī)范的話(huà),我們書(shū)寫(xiě) OOP 的代碼的時(shí)候,就顯得比較簡(jiǎn)單了,因?yàn)槟銓?duì)面向?qū)ο髞?lái)說(shuō),有了一個(gè)非常體系化的認(rèn)知。

可能你接觸過(guò)一些編程規(guī)范,比如控制反轉(zhuǎn)(Inversion of Controlling,簡(jiǎn)稱(chēng) IoC)、依賴(lài)注入(Dependency Injection,簡(jiǎn)稱(chēng) DI)之類(lèi)的。這些不在我們這里的闡述范疇,因?yàn)樗鼈儽旧砭蛯儆谝环N復(fù)雜的處理機(jī)制,而跟這里所說(shuō)的 SOLID 五大原則是沒(méi)有關(guān)系(或者關(guān)系很小)的。如果你想學(xué)習(xí)的話(huà),我可能會(huì)在以后開(kāi)設(shè)高階 C# 編程教程的相關(guān)內(nèi)容,比如 CLR 視角的 C# 啊、C# 的編程規(guī)范之類(lèi)的東西,在那里可能會(huì)有所涉及。所以本教程并不對(duì)這些高級(jí)編程技巧做任何闡述。

另外,正是因?yàn)檫@五個(gè)規(guī)范的首字母恰好可以拼成一個(gè)單詞 solid,而 solid 這個(gè)單詞原本的意思是“穩(wěn)固的”、“堅(jiān)固的”、“固體”的意思,因此這也雙關(guān)暗示了程序使用這五大規(guī)范寫(xiě)出來(lái)之后,程序的健壯性得到保障(畢竟,更“穩(wěn)固”了嘛)。好吧這句話(huà)是從劉鐵猛老師這里直接拿過(guò)來(lái)的

Part 1 單一職責(zé)原則

1-1 描述

我們來(lái)看一張 meme 圖。

圖片呈現(xiàn)的就是違背“單一職責(zé)原則”的工具:瑞士軍刀。單一職責(zé)原則說(shuō):

Every software module should have only one reason to change.

每一個(gè)軟件的模塊都只能有一個(gè)修改它的原因。

這句話(huà)從字面意思上來(lái)看,實(shí)際上并不是很好理解。“何為‘只能有一個(gè)修改它的原因’?”,這樣的疑問(wèn)。下面我們來(lái)舉個(gè)例子告訴大家,什么樣的代碼是違反了單一職責(zé)原則。

圖里的下面文字的意思是“這僅僅表示你能這么做,但這不代表你應(yīng)該這么做”。

1-2 舉例

如代碼所示。這個(gè) UserService 類(lèi)型里包含了三個(gè)方法,分別是注冊(cè)(Register 方法)、驗(yàn)證電子郵件(ValidateEmail 方法)和發(fā)送電子郵件(SendEmail 方法)。我們暫且不管類(lèi)的訪問(wèn)修飾符、virtual 這些修飾符給我們帶來(lái)的用途,因?yàn)楸疚慕榻B的內(nèi)容跟它們暫時(shí)沒(méi)有關(guān)系,僅是一個(gè)示例程序。

這個(gè)類(lèi)型的名字翻譯過(guò)來(lái)是“用戶(hù)服務(wù)”,也就意味著這個(gè)類(lèi)型包含的操作行為都是跟用戶(hù)相關(guān)的。不過(guò),“驗(yàn)證郵件”和“發(fā)送郵件”并不是“用戶(hù)”的行為,而是“郵件”的行為。可能你會(huì)認(rèn)為,發(fā)送郵件肯定是人在做這個(gè)事情啊,那為什么不算是用戶(hù)的行為呢?請(qǐng)注意類(lèi)型名。類(lèi)型叫做 UserService 就意味著里面包含容納的操作行為都應(yīng)該是和用戶(hù)息息相關(guān)的;而發(fā)送郵件和驗(yàn)證郵件是看郵件到底正常與否,完全不需要用戶(hù)參與執(zhí)行(請(qǐng)看代碼:代碼執(zhí)行期間都沒(méi)有使用跟“用戶(hù)”有關(guān)的任何東西)。因此,UserService 完成的任務(wù)超出了 UserService 類(lèi)型名稱(chēng)賜予的執(zhí)行范圍和能力。

我們把“一個(gè)類(lèi)型完成或者做了這個(gè)類(lèi)型不該做的操作”叫做違反了單一職責(zé)原則。

1-3 修正用法

修正代碼的邏輯,改為單一職責(zé)原則的辦法也比較簡(jiǎn)單。因?yàn)橐粋€(gè)類(lèi)型只做這個(gè)類(lèi)型該做的事情,所以和這個(gè)類(lèi)型無(wú)關(guān)的行為都需要額外存儲(chǔ)一個(gè)單獨(dú)的類(lèi)型。

例如 1-2 給出的錯(cuò)誤例子里,我們建議大家為這兩個(gè)郵件相關(guān)的行為抽取出一個(gè)單獨(dú)的類(lèi)型,名字就建議改成 EmailService 即可:

我們抽取出 EmailService 類(lèi)型,專(zhuān)門(mén)綁定和書(shū)寫(xiě)有關(guān)電子郵件本身的操作和行為;而在 UserService 類(lèi)型里,我們只需要寫(xiě)跟 UserService 有關(guān)的行為就可以了。

Part 2 開(kāi)閉原則

2-1 描述

老規(guī)矩,我們先來(lái)看 meme 圖。

開(kāi)閉原則供奉的一句話(huà)是:

A software module/class is open for extension and closed for modification.

一個(gè)軟件模塊或者類(lèi)型只能為拓展而開(kāi)放,而為修改而不開(kāi)放。

這句話(huà)是什么意思呢?

圖下面的那句話(huà)的意思是“你都穿了外套,那開(kāi)胸外科醫(yī)生來(lái)干嘛”。想想是不是這個(gè)道理:外科醫(yī)生(外部用戶(hù))是用來(lái)變動(dòng)修改你、醫(yī)治你的身體(變動(dòng) API)的;這個(gè)時(shí)候你穿外套(封閉起來(lái))干嘛。

下面我們來(lái)看一個(gè)例子,看看什么樣的例子違反了開(kāi)閉原則。

2-2 舉例

乍一看,這個(gè)方法沒(méi)有什么問(wèn)題,但第 11 行給出的 TotalArea 方法違背了開(kāi)閉原則。為什么呢?因?yàn)閰?shù)是 Rectangle 類(lèi)型的數(shù)組。要知道 Rectangle[] 整體是數(shù)組,數(shù)組是引用類(lèi)型的對(duì)象,這意味著你在傳入該參數(shù)的時(shí)候,就不能保證這個(gè)方法里到底改沒(méi)有改變這個(gè)數(shù)組里面的對(duì)象。雖然我們按上帝視角看到了這個(gè)方法里的源代碼,但調(diào)用方并不清楚里面的代碼,如果有修改變動(dòng)的話(huà),這個(gè)方法就不算封裝良好。這個(gè) TotalArea 方法一般看名字就知道是計(jì)算數(shù)組里給出的所有 Rectangle 實(shí)例的總面積的。既然是計(jì)算總和,那么讀取數(shù)據(jù)就好了,變動(dòng)里面的元素是不是就有點(diǎn)奇怪?

雖然我們可以這么說(shuō),但我們僅憑經(jīng)驗(yàn)主義去假設(shè)方法沒(méi)有修改元素,是不是有點(diǎn)說(shuō)不過(guò)去?所以,里面方法什么樣是對(duì)于用戶(hù)來(lái)說(shuō)未知的。那么,作為“盲盒”來(lái)看的話(huà),我們就不能把方法的參數(shù)設(shè)計(jì)成傳入具體類(lèi)型的對(duì)象。這種代碼就算是違反了開(kāi)閉原則。

2-3 修正用法

為了避免違反開(kāi)閉原則的代碼,我們建議參數(shù)改成模糊類(lèi)型,避免用戶(hù)猜測(cè)類(lèi)型。在設(shè)計(jì)方法的層面,程序員也為了避免去變更數(shù)據(jù),使用模糊類(lèi)型來(lái)完成任務(wù)處理。

比如改成這樣??捎械耐瑢W(xué)會(huì)提出疑問(wèn):我用 object 是不是有點(diǎn)小題大做了?是的,略微有一點(diǎn)。如果 Rectangle 具有自己定義的基類(lèi)型的話(huà),那么 object 就不必了。

比如說(shuō),我們寫(xiě)一個(gè) Shape 抽象類(lèi)型,讓 Rectangle 從這個(gè)類(lèi)型派生:

至于 TotalArea 方法的話(huà):

由于我們只需要完成計(jì)算總面積和的操作,我們繼承的 Shape 抽象類(lèi)型的 Area 屬性的重寫(xiě)就派上用場(chǎng)了。而且,計(jì)算總面積和的操作,只需要使用到這個(gè)類(lèi)型的 Area 屬性,因此我們無(wú)需暴露出這個(gè) Shape 類(lèi)型的別的信息,比如假設(shè)我派生了一個(gè)圓形類(lèi)型 Circle,顯然計(jì)算面積需要用到圓周率 \pi 的值。如果我們嘗試在 Shape 類(lèi)型里公開(kāi)化這個(gè)變量的話(huà):

顯然這樣的代碼就不夠封裝。Pi 字段即使無(wú)法修改,但因?yàn)樗惶峁┙o Shape 和其派生類(lèi)使用,因此暴露出來(lái)并沒(méi)有意義。所以不要什么都暴露出來(lái)。

總之,這么書(shū)寫(xiě)代碼(盡量使用基類(lèi)型)可以有效解決和修正違背開(kāi)閉原則的代碼。

Part 3 李斯柯夫替換原則

李斯柯夫是一個(gè)人,美國(guó)計(jì)算機(jī)科學(xué)家,圖靈獎(jiǎng)和約翰·馮諾依曼獎(jiǎng)得主?,F(xiàn)在在 MIT(麻省理工附屬小學(xué)學(xué)院)電子電氣與計(jì)算機(jī)科學(xué)系擔(dān)任教授。這個(gè)原則就是她提出的。

是的,她是女的。

因?yàn)槭且糇g,所以里氏替換原則里面的“里”是李斯柯夫替換原則的“李”是同音的不同翻譯方式。

3-1 描述

下面我們來(lái)看 meme 圖。

里氏替換原則的原話(huà)是這樣的:

You should be able to use any derived class instead of a parent class and have it behave in the same manner without modification.

你應(yīng)該(在設(shè)計(jì)代碼的時(shí)候)能用所有派生類(lèi)型來(lái)代替基類(lèi)型,還要擁有表現(xiàn)出(和基類(lèi)型表現(xiàn)出來(lái)的)相同的修改行為的方式。

這句話(huà)的意思是,你需要設(shè)計(jì)一個(gè)類(lèi)型,能夠讓類(lèi)型的基類(lèi)型和本類(lèi)型表現(xiàn)出同樣的修改方式。還是不太懂的話(huà),我們就來(lái)看例子。

上面圖里下面的文字翻譯出來(lái)是“它看起來(lái)就跟個(gè)鴨子一樣——叫起來(lái)確實(shí)是個(gè)鴨子的叫聲,但是它很有可能還需要裝電池。這就意味著你可能封裝抽象化類(lèi)型的時(shí)候有一個(gè)錯(cuò)誤的設(shè)計(jì)”。

3-2 舉例

假設(shè)我有一個(gè)程序?qū)崿F(xiàn)了一個(gè) Shape 類(lèi)型,并派生出了子類(lèi)型。

請(qǐng)注意這里的 Area 屬性。在 Shape 類(lèi)型里,我們嘗試抽象了 Area 屬性,但問(wèn)題出在這里我們還給其設(shè)置了 set 方法。如果我們這么去設(shè)置對(duì)象的話(huà),程序就會(huì)出現(xiàn)一個(gè)問(wèn)題:子類(lèi)型重寫(xiě)的時(shí)候需要重寫(xiě)掉 set 方法,然后改變?cè)镜膱?zhí)行邏輯。顯然,重寫(xiě)后的 get 方法是沒(méi)有問(wèn)題的,但是 set 方法卻有問(wèn)題:它給 _width_height 都改成 0 了。那程序運(yùn)行起來(lái)不出錯(cuò)才怪。

所以,Shape 類(lèi)型的設(shè)計(jì),以及 Rectangle 類(lèi)型的重寫(xiě) Area 的行為違背了里氏替換原則:因?yàn)?AreaShape 里本想用一個(gè) _area 緩存計(jì)算結(jié)果,然后想讓用戶(hù)派生出來(lái)的時(shí)候,直接通過(guò) _area 存取數(shù)值。但是封裝錯(cuò)誤的地方在于,子類(lèi)型可以重寫(xiě),就意味著它可能會(huì)被改變執(zhí)行邏輯。比如這里 Rectangle 類(lèi)型里 Area 屬性的 set 方法。

3-3 修正用法

解決修正這個(gè)問(wèn)題的辦法很簡(jiǎn)單,只要避免基類(lèi)型的行為改變即可。

我們只需要去掉 virtual 關(guān)鍵字防止重寫(xiě),然后找到合適的時(shí)機(jī)把計(jì)算結(jié)果給賦值到基類(lèi)型里的 _area 字段里去,就可以了。比如上面的代碼就是一種解決方案:我們通過(guò)抽取抽象類(lèi)型的構(gòu)造器的方式,讓 Rectangle 類(lèi)型調(diào)用此構(gòu)造器,然后把 width * height 的結(jié)果賦值過(guò)去,這樣就達(dá)到了改變掉底層 _area 的數(shù)值結(jié)果。

另外一種實(shí)現(xiàn)方式就是,直接砍掉 _area 字段。因?yàn)榫彺鎸?duì)于這里來(lái)說(shuō)并不是合適的封裝。雖然你的想法可能是為了程序執(zhí)行更流暢,直接把結(jié)果提前算出來(lái),到時(shí)候取出來(lái)就是了,但是這樣的封裝行為有可能導(dǎo)致潛在的問(wèn)題和代碼設(shè)計(jì)的復(fù)雜度的提升。

只要砍掉了 _area 字段,我們就直接封裝出 Area 屬性,并直接抽象化它就可以了;與此同時(shí),我們?yōu)榱朔庋b良好,只需要讓派生類(lèi)型實(shí)現(xiàn) get 方法即可,我們就不管 set 方法了。

Part 4 接口隔離原則

4-1 描述

來(lái)看 meme 圖。

接口隔離原則說(shuō)的是:

That clients should not be forced to implement interfaces they don't use. Instead of one fat interface, many small interfaces are preferred based on groups of methods, each one serving one submodule.

客戶(hù)端不應(yīng)該強(qiáng)制去實(shí)現(xiàn)它們壓根用不上的接口。我們更傾向于使用若干瘦接口來(lái)代替一個(gè)胖接口去抽取方法集,每一個(gè)接口都只為一個(gè)子模塊服務(wù)。

這句話(huà)的意思是,一個(gè)接口只管一個(gè)表現(xiàn)行為,不要圖省事就啥都放到一個(gè)接口里去。

圖里的話(huà)說(shuō)的是:“難不成你還想把我這個(gè) USB 插進(jìn)這個(gè)機(jī)器里?那你說(shuō)插哪兒?”。

4-2 舉例

下面來(lái)舉個(gè)例子告訴大家,什么樣的代碼是違反了接口隔離原則的。

假設(shè)我有一個(gè)接口 ILead 表示一個(gè)領(lǐng)導(dǎo)層的人士需要做的事情,我們可能會(huì)有如下的一些行為:

很顯然,這樣的接口設(shè)計(jì)就不是很合理。這里面包含了三個(gè)方法,分別是“創(chuàng)建子任務(wù)”、“分配任務(wù)”和“完成任務(wù)”??蓡?wèn)題就在于,領(lǐng)導(dǎo)層的人需要自己做事情嗎?領(lǐng)導(dǎo)層的人不都是委派任務(wù)給下屬完成嗎?既然如此,那么把 WorkOnTask 寫(xiě)進(jìn)接口里去是為了什么呢?難不成領(lǐng)導(dǎo)層也得 WorkOnTask?這就不合理了。這樣的接口就顯得相當(dāng)不合理,違背了接口隔離原則。

4-3 修正用法

解決這一點(diǎn)的辦法很簡(jiǎn)單,就是把這個(gè)額外的 WorkOnTask 抽取出來(lái)單獨(dú)存到一個(gè)接口里面去。比如像是這樣:

然后原本的 ILead 里只包含剩下那兩個(gè)方法即可。那么,程序員做程序員做的事情(WorkOnTask),而領(lǐng)導(dǎo)做領(lǐng)導(dǎo)做的事情(CreateSubTaskAssignTask)。

就算是極端情況——組織協(xié)作者(假設(shè)叫 TeamLead 類(lèi)型)既要委派任務(wù),也要自己做任務(wù)的話(huà),那么這個(gè)類(lèi)型直接倆接口都實(shí)現(xiàn)就可以了:

這才是合適的實(shí)現(xiàn)。

Part 5 依賴(lài)反轉(zhuǎn)原則

5-1 描述

來(lái)看 meme 圖。

依賴(lài)反轉(zhuǎn)原則說(shuō)的是:

High-level modules/classes should not depend on low-level modules/classes. Both should depend upon abstractions. Secondly, abstractions should not depend upon details. Details should depend upon abstractions.

高級(jí)別的模塊或者類(lèi)就不應(yīng)該去依賴(lài)于低級(jí)別的模塊或者類(lèi),而它們都需要單獨(dú)抽象出一個(gè)東西出來(lái),讓這倆都引用。另外,抽象出來(lái)的東西也不應(yīng)該依賴(lài)于一些細(xì)節(jié),這些細(xì)節(jié)也不應(yīng)該依賴(lài)于這個(gè)抽象(即雙方都互不依賴(lài))。

圖上說(shuō)的文字是“難道你要把一個(gè)臺(tái)燈直接焊到墻上的電線去?”。這句話(huà)想說(shuō)的是,焊接工具做的是焊接工具的事情,線圈是線圈的事情,不能把操作給反過(guò)來(lái)。臺(tái)燈的線怎么接到上面去,不是看你焊接工具怎么做,而是看你臺(tái)燈怎么做。

5-2 舉例

假設(shè)我有一個(gè)客戶(hù)端(Client 類(lèi)型),它想要調(diào)取你的手機(jī),完成打開(kāi)支付功能的操作。你的手機(jī)包含很多 App,比如某付寶、某寶、某信和某 Q 之類(lèi)的。為了我們實(shí)現(xiàn)起來(lái)輕松呢,手機(jī)打開(kāi)這些操作我們可能會(huì)用到 Phone 類(lèi)型封裝一下這些方法進(jìn)去。也就是說(shuō),Phone 類(lèi)型里包含了如何調(diào)取打開(kāi)每一個(gè) App 的支付功能,而 Client 的工作呢,就是調(diào)取 Phone 類(lèi)型里的這些支付功能。

現(xiàn)在,從代碼層面來(lái)看,邏輯是清晰的:

問(wèn)題是,假設(shè)我要開(kāi)某信的 App 的接入的話(huà),我的 Phone 類(lèi)型執(zhí)行要改變不說(shuō),我的 Client 類(lèi)型的代碼也得改。因?yàn)?Client 類(lèi)型是要調(diào)用 Phone 類(lèi)型里的這些操作的,而 Phone 類(lèi)型因?yàn)?App 的接入,也得寫(xiě)上對(duì)應(yīng)的對(duì)接代碼。

這樣就很不方便。我要改變兩個(gè)地方。這種不算是違背依賴(lài)反轉(zhuǎn)原則,但它屬于不合理的類(lèi)型設(shè)計(jì),我們需要規(guī)范化這個(gè)代碼。

5-3 修正用法

為了改為依賴(lài)倒置,我們需要?jiǎng)?chuàng)建一個(gè) IApp 的接口,讓 QQTaoBao 類(lèi)型去實(shí)現(xiàn)這個(gè) IApp 接口;與此同時(shí),我們只需要在 Phone 類(lèi)型里完成一個(gè)傳入 IApp 接口類(lèi)型的對(duì)象就可以處理的操作就可以了。

然后在 Phone 類(lèi)型里:

最后在 Client 里調(diào)用這個(gè) OpenApp 方法,豈不美哉?

我們通過(guò)接口的方式,讓 Phone 和底層的 App 類(lèi)型都從這里依賴(lài)出來(lái),就好比翻轉(zhuǎn)了一下依賴(lài),因此稱(chēng)為依賴(lài)反轉(zhuǎn)原則。

那么至此我們就把本篇章的內(nèi)容給大家介紹完了。下一節(jié)的內(nèi)容是反射。

第 56 講:類(lèi)型良構(gòu)規(guī)范(六):SOLID 原則的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
瑞丽市| 三台县| 上栗县| 合水县| 亚东县| 青龙| 广宁县| 玉门市| 佛学| 兰坪| 嘉禾县| 遂平县| 礼泉县| 新民市| 讷河市| 廉江市| 岳池县| 伊春市| 兴业县| 尼玛县| 江山市| 南江县| 灵璧县| 竹山县| 阜阳市| 长垣县| 蛟河市| 天祝| 雅江县| 日照市| 苏尼特左旗| 四川省| 开平市| 顺义区| 东辽县| 桐梓县| 泰和县| 萍乡市| 罗田县| 定州市| 沐川县|