摘要:同時(shí),在方法命名上也投入一精力,盡可能地使方法名保持簡(jiǎn)單,它將幫助你在重構(gòu)代碼時(shí),更好的達(dá)到單一職責(zé)。
這是理解SOLID原則中,關(guān)于單一職責(zé)原則如何幫助我們編寫(xiě)低耦合和高內(nèi)聚的第二篇文章。單一職責(zé)原則是什么
之前的第一篇文章闡述了依賴倒置原則(DIP)能夠使我們編寫(xiě)的代碼變得低耦合,同時(shí)具有很好的可測(cè)試性,接下來(lái)我們來(lái)簡(jiǎn)單了解下單一職責(zé)原則的基本概念:
Every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.每一個(gè)模塊或者類(lèi)所對(duì)應(yīng)的職責(zé),應(yīng)對(duì)應(yīng)系統(tǒng)若干功能中的某個(gè)單一部分,同時(shí)關(guān)于該職責(zé)的封裝都應(yīng)當(dāng)通過(guò)這個(gè)類(lèi)來(lái)完成。
往簡(jiǎn)單來(lái)講:
A class or module should have one, and only one, reason to be changed.一個(gè)類(lèi)或者模塊應(yīng)當(dāng)用于單一的,并且唯一的緣由被更改。
如果僅僅通過(guò)這兩句話去理解, 一個(gè)類(lèi)或者模塊如果如果越簡(jiǎn)單(具有單一職責(zé)),那么這個(gè)類(lèi)或者模塊就越容易被更改是有一些困難的。為了便于我們理解整個(gè)概念,我們將分別從三個(gè)不同的角度來(lái)分析這句話,這三個(gè)角度是:
Single: 單一
Responsibility: 職責(zé)
Change: 改變
什么是單一Only one; not one of several.唯一的,而不是多個(gè)中的某個(gè)。
Synonyms: one, one only, sole, lone, solitary, isolated, by itself.
同義詞:一,僅有的一個(gè),唯一,獨(dú)個(gè),獨(dú)自存在的,孤立的,僅自己。
單一意味著某些工作是獨(dú)立的。比如,在類(lèi)中,類(lèi)方法僅完成某家獨(dú)立的事情,而不是兩件,如下:
class UserComponent { // 這是第一件事情,獲取用戶詳情數(shù)據(jù) getUserInfo(id) { this.api.getUserInfo(id).then(saveToState) } // 這是第二件事情,渲染視圖的邏輯 render() { const { userInfo } = this.state; return} }
- Name: { userInfo.name }
- Surname: { userInfo.surname }
- Email: { userInfo.email }
看了上面的代碼,你可能很快就會(huì)聯(lián)想到,這些代碼基本存在于所有的React組件中。
確實(shí),對(duì)于一些小型的項(xiàng)目或者演示型項(xiàng)目,這樣編寫(xiě)代碼不會(huì)產(chǎn)生太大的問(wèn)題。但是如果在大型或者復(fù)雜度很高的項(xiàng)目中,仍然按照這樣的風(fēng)格,則是一件比較糟糕的事情,因?yàn)橐粋€(gè)組件往往做了它本不應(yīng)當(dāng)做的事情(承擔(dān)了過(guò)多的職責(zé))。
這樣會(huì)帶來(lái)什么壞處呢?比如對(duì)于以上的api服務(wù),在將來(lái)的某天你做出了一些修改,增加了一些額外的邏輯,那么為了使代碼能夠正常工作,你至少需要修改項(xiàng)目中的兩個(gè)地方以適應(yīng)這個(gè)修改,一處修改是在API服務(wù)中,而另一處則在你的組件中。如果進(jìn)一步思考的,我們會(huì)發(fā)現(xiàn),修改次數(shù)與在項(xiàng)目直接使用API服務(wù)的次數(shù)成正比,如果項(xiàng)目足夠復(fù)雜,足夠大,一處簡(jiǎn)單的邏輯修改,就需要做出一次貫穿整個(gè)系統(tǒng)的適配工作。
那么我們?nèi)绻苊膺@種情況的發(fā)生呢?很簡(jiǎn)單,我們僅僅需要將關(guān)于用戶詳情數(shù)據(jù)的邏輯提升到調(diào)用層,在上面的例子中,我們應(yīng)當(dāng)使用React.component.prop來(lái)接受用戶詳情數(shù)據(jù)。這樣,UserComponent組件的工作不再與如何獲取用戶詳情數(shù)據(jù)的邏輯耦合,從而變得單一。
對(duì)于鑒別什么是單一,什么不是單一,有很多不同的方式。一般來(lái)說(shuō),只需要牢記,讓你的代碼盡可能的少的去了解它已經(jīng)做的工作。(譯者注:我理解意思應(yīng)當(dāng)是,應(yīng)當(dāng)盡可能的讓已有的類(lèi)或者方法變得簡(jiǎn)單、輕量,不需要所有事情都親自為之)
總之,不要讓你的對(duì)象成為上帝對(duì)象。
A God Object aka an Object that knows everything and does everything.什么是職責(zé)上帝對(duì)象,一個(gè)知道一切事情,完成一切事情的對(duì)象。
In object-oriented programming, a God object is an object that knows too much or does too much. The God object is an example of an anti-pattern.
在面向?qū)ο缶幊讨?,上帝?duì)象指一個(gè)了解太情或者做太多事情的對(duì)象。上帝對(duì)象是反模式的一個(gè)典型。
職責(zé)指軟件系統(tǒng)中,每一個(gè)指派給特定方法、類(lèi)、包和模塊所完成的工作或者動(dòng)作。
Too much responsibility leads to coupling.太多的職責(zé)導(dǎo)致耦合。
耦合性代表一個(gè)系統(tǒng)中某個(gè)部分對(duì)系統(tǒng)中另一個(gè)部分的了解程度。舉個(gè)例子,如果一段客戶端代碼在調(diào)用class A的過(guò)程中,必須要先了解有關(guān)class B的細(xì)節(jié),那么我們說(shuō)A和B耦合在了一起。通常來(lái)說(shuō),這是一件糟糕的事情。因?yàn)樗鼤?huì)使針對(duì)系統(tǒng)本身的變更復(fù)雜化,同時(shí)會(huì)在長(zhǎng)期越來(lái)越糟。
為了使一個(gè)系統(tǒng)到達(dá)適當(dāng)?shù)鸟詈隙?,我們需要在以下三個(gè)方面做出調(diào)整
組件的內(nèi)聚性
如何測(cè)量每個(gè)組件的預(yù)期任務(wù)
組件如何專注于任務(wù)本身
低內(nèi)聚性的組件在完成任務(wù)時(shí),和它們本身的職責(zé)關(guān)聯(lián)并不緊密。比如,我們現(xiàn)在有一個(gè)User類(lèi),這個(gè)類(lèi)中我們保存了一些基本信息:
class User { public age; public name; public slug; public email; }
對(duì)于屬性本身,如果對(duì)于每個(gè)屬性聲明一些getter或者setter方法是沒(méi)什么問(wèn)題的。但是如果我們加一些別的方法,比如:
class User { public age; public name; public slug; public email; // 我們?yōu)槭裁匆幸韵逻@些方法? checkAge(); validateEmail(); slugifyName(); }
對(duì)于checkAge、validateEmail、slugifyName的職責(zé),與Userclass本身關(guān)系并不緊密,因此就會(huì)這些方法就會(huì)使User的內(nèi)聚性變低。
仔細(xì)思考的話,這些方法的職責(zé)和校驗(yàn)和格式化用戶信息的關(guān)系更緊密,因此,它們應(yīng)當(dāng)從User中被抽離出來(lái),放入到另一個(gè)獨(dú)立的UserFieldValidation類(lèi)中,比如:
class User { public age; public name; public slug; public email; } class UserFieldValidation { checkAge(); validateEmail(); slugifyName(); }什么是變更
變更指對(duì)于已存在代碼的修改或者改變。
那么問(wèn)題來(lái)了,什么原因迫使我們需要對(duì)源碼進(jìn)行變更?從眾多過(guò)期的軟件系統(tǒng)的歷史數(shù)據(jù)的研究來(lái)看,大體有三方面原因促使我們需要作出變更:
增加新功能
修復(fù)缺陷或者bug
重構(gòu)代碼以適配將來(lái)作出的變更
做為一個(gè)程序員,我們天天不都在做這三件事情嗎?讓我們來(lái)用一個(gè)例子完整的看一下什么是變更,比方說(shuō)我們完成了一個(gè)組件,現(xiàn)在這個(gè)組件性能非常好,而且可讀性也非常好,也許是你整個(gè)職業(yè)生涯中寫(xiě)的最好的一個(gè)組件了,所以我們給它一個(gè)炫酷的名字叫作SuperDuper(譯者注:這個(gè)名字的意思是超級(jí)大騙子)
class SuperDuper { makeThingsFastAndEasy() { // Super readable and efficient code } }
之后過(guò)了一段時(shí)間,在某一天,你的經(jīng)理要求你增加一個(gè)新功能,比如說(shuō)去調(diào)用別的class中的每個(gè)函數(shù),從而可以使當(dāng)前這個(gè)組件完成更多的工作。你決定將這個(gè)類(lèi)以參數(shù)的形式傳入構(gòu)造方法,并在你的方法調(diào)用它。
這個(gè)需求很簡(jiǎn)單,只需要增加一行調(diào)用的代碼即可,然后你做了以下變更(增加新功能):
class SuperDuper { constructor(notDuper: NotSoDuper) { this.notDuper = notDuper } makeThingsFastAndEasy() { // Super readable and efficient code this.notDuper.invokeSomeMethod() } }
好了,之后你針對(duì)你做的變更代碼運(yùn)行了單元測(cè)試,然后你突然發(fā)現(xiàn)這條簡(jiǎn)單的代碼使100多條的測(cè)試用例失敗了。具體原因是因?yàn)樵谡{(diào)用notDuper方法之前,你需要針對(duì)一些額外的業(yè)務(wù)邏輯增加條件判斷來(lái)決定是否調(diào)用它。
于是你針對(duì)這個(gè)問(wèn)題又進(jìn)行了一次變更(修復(fù)缺陷或者bug),或許還會(huì)針對(duì)一些別的邊界條件進(jìn)行一些額外的修復(fù)和改動(dòng):
class SuperDuper { constructor(notDuper: NotSoDuper) { this.notDuper = notDuper } makeThingsFastAndEasy() { // Super readable and efficient code if (someCondition) { this.notDuper.invokeSomeMethod() } else { this.callInternalMethod() } } }
又過(guò)了一段時(shí)間,因?yàn)檫@個(gè)SuperDuper畢竟是你職業(yè)生涯完成的最棒的類(lèi),但是當(dāng)前調(diào)用noDuper的方法實(shí)在是有點(diǎn)不夠逼格,于是你決定引入事件驅(qū)動(dòng)的理念來(lái)達(dá)到不在SuperDuper內(nèi)部直接調(diào)用noDuper方法的目的。
這次實(shí)際是對(duì)已經(jīng)代碼的一次重構(gòu)工作,你引入了事件驅(qū)動(dòng)模型,并對(duì)已有的代碼做出了變更(重構(gòu)代碼以適配將來(lái)作出的變更):
class SuperDuper { makeThingsFastAndEasy() { // Super readable and efficient code ... dispatcher.send(actionForTheNotDuper(payload)) // Send a signal } }
現(xiàn)在再來(lái)看我們的SuperDuper類(lèi),已經(jīng)和最原始的樣子完全不一樣了,因?yàn)槟惚仨氠槍?duì)新的需求、存在的缺陷和bug或者適配新的軟件架構(gòu)而做出變更。
因此為了便于我們做出變更,在代碼的組織方式上,我們需要用心,這樣才會(huì)使我們?cè)谧龀鲎兏鼤r(shí)更加容易。
如何才能使代碼貼近這些原則很簡(jiǎn)單,只需要牢記,使代碼保持足夠簡(jiǎn)單。
Gather together the things that change for the same reasons. Separate those things that change for different reasons.孤立變化將由于相同原因而做出改變的東西聚集在一起,將由于不同原因而做出改變的東西彼此分離。
對(duì)于所編寫(xiě)的做出變更的代碼,你需要仔細(xì)的檢查它們,無(wú)論是從整體檢查,還是有邏輯的分而治之,都可以達(dá)到孤立變化的目的。你需要更多的了解你所編寫(xiě)的代碼,比如,為什么這樣寫(xiě),代碼到底做了什么等等,并且,對(duì)于一些特別長(zhǎng)的方法和類(lèi)要格外關(guān)注。
Big is bad, small is good…追蹤依賴大即是壞,小即是好。
對(duì)于一個(gè)類(lèi),檢查它的構(gòu)造方法是否包含了太多的參數(shù),因?yàn)槊恳粋€(gè)參數(shù)都作為這個(gè)類(lèi)的依賴存在,同時(shí)這些參數(shù)也擁有自身的依賴。如果可能的話,使用DI機(jī)制來(lái)動(dòng)態(tài)的注入它們。
Use Dependency Injection追蹤方法參數(shù)使用依賴注入
對(duì)于一個(gè)方法,檢查它是否包含了太多參數(shù),一般來(lái)講,一個(gè)方法的參數(shù)個(gè)數(shù)往往代表了其內(nèi)部所實(shí)現(xiàn)的職能。
同時(shí),在方法命名上也投入一精力,盡可能地使方法名保持簡(jiǎn)單,它將幫助你在重構(gòu)代碼時(shí),更好的達(dá)到單一職責(zé)。長(zhǎng)的函數(shù)名稱往往意味著其內(nèi)部有糟糕的味道。
Name things descriptively盡早重構(gòu)描述性命名。
盡可能早的重構(gòu)代碼,當(dāng)你看到一些代碼可以以更簡(jiǎn)明的方式進(jìn)行時(shí),重構(gòu)它。這將幫助你在項(xiàng)目進(jìn)行的整個(gè)周期不斷的整理代碼以便于更好的重構(gòu)。
Refactor to Design Patterns善于做出改變按設(shè)計(jì)模式重構(gòu)代碼
最后,在需要做出改變時(shí),果斷地去做。當(dāng)然這些改變會(huì)使系統(tǒng)的耦合性更低,內(nèi)聚性更高,而不是往相反的方向,這樣你的代碼會(huì)一直建立在這些原則之上。
Introduce change where it matters. Keep things simple but not simpler.譯者注在重要的地方介紹改變。保持事情的簡(jiǎn)單性,但不是一味追求簡(jiǎn)單。
單一職責(zé)原則其實(shí)在我們?nèi)粘9ぷ髦薪?jīng)常會(huì)接觸到,比方說(shuō)
我們經(jīng)常會(huì)聽(tīng)到DIY(dont repeat yourself)原則,其本身就是單一職責(zé)的一個(gè)縮影,為了達(dá)到DIY,對(duì)于代碼中的一些通用方法,我們經(jīng)常會(huì)抽離到獨(dú)立的utils目錄甚至編寫(xiě)為獨(dú)立的工具函數(shù)庫(kù), 比如lodash和ramda等等
OAOO, 指Once And Only Once, 原則本身的含義可以自行搜索,實(shí)際工作中我們對(duì)于相同只能模塊的代碼應(yīng)當(dāng)盡可能去在抽象層合并它們,提供抽象類(lèi),之后通過(guò)繼承的方式來(lái)滿足不同的需求
我們都會(huì)很熟悉單例模式這個(gè)模式,但在使用時(shí)一定要小心,因?yàn)楸举|(zhì)上單例模式與單一職責(zé)原則相悖,在實(shí)踐中一定要具體情況具體分析。同時(shí)也不要過(guò)度優(yōu)化,就如同文章中最后一部分提及的,我們要保證一件事情的簡(jiǎn)單性,但不是一味地為了簡(jiǎn)單而簡(jiǎn)單。
前端的技術(shù)棧中,redux對(duì)于數(shù)據(jù)流層的架構(gòu)思想,便充分體現(xiàn)了單一職責(zé)原則的重要性,action作為對(duì)具體行為的抽象, store用來(lái)描述應(yīng)用的狀態(tài),reducer作為針對(duì)不同行為如何對(duì)store作出修改的抽象。
react中經(jīng)常提及的木偶組件(dump component)其實(shí)和文章中第一部分的例子如出一轍
工廠模式和命令模式也一定程度體現(xiàn)了單一職責(zé)原則,前者對(duì)于作為生產(chǎn)者存在并不需要關(guān)心消費(fèi)者如何消費(fèi)對(duì)象實(shí)例,后者以命令的方式封裝功能本身就是單一職責(zé)原則的體現(xiàn)。
我能夠想到的就這么多,寫(xiě)的比較亂,拋磚引玉,如有錯(cuò)誤,還望指正。
關(guān)注公眾號(hào) 全棧101,只談技術(shù),不談人生
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/107174.html
摘要:接口隔離原則是什么客戶端代碼不應(yīng)當(dāng)被迫依賴于它們不需要的方法。 這是理解SOLID原則,關(guān)于接口隔離原則如何幫助我們創(chuàng)建簡(jiǎn)單的抽象接口,并使客戶端代與接口之間存在的更少的依賴關(guān)系。 接口隔離原則是什么 Clients should not be forced to depend on methods that they do not use.客戶端代碼不應(yīng)當(dāng)被迫依賴于它們不需要的方法。...
摘要:事件驅(qū)動(dòng)模型對(duì)于一些復(fù)雜的事件驅(qū)動(dòng)模型,比如拖拽,往往使用開(kāi)閉原則會(huì)達(dá)到意想不到的效果。 這是理解SOLID原則,介紹什么是開(kāi)閉原則以及它為什么能夠在對(duì)已有的軟件系統(tǒng)或者模塊提供新功能時(shí),避免不必要的更改(重復(fù)勞動(dòng))。 開(kāi)閉原則是什么 Software entities (classes, modules, functions, etc.) should be open for ext...
摘要:什么是里氏替換原則某個(gè)對(duì)象實(shí)例的子類(lèi)實(shí)例應(yīng)當(dāng)可以在不影響程序正確性的基礎(chǔ)上替換它們。除了在編程語(yǔ)言層面,在前端實(shí)際工作中,你可能會(huì)聽(tīng)到一個(gè)叫作的概念,這個(gè)概念我認(rèn)為也是里氏替換原則的一直延伸。 這是理解SOLID原則,關(guān)于里氏替換原則為什么提倡我們面向抽象層編程而不是具體實(shí)現(xiàn)層,以及為什么這樣可以使代碼更具維護(hù)性和復(fù)用性。 什么是里氏替換原則 Objects should be rep...
摘要:設(shè)計(jì)原則梳理,參考核心技術(shù)與最佳實(shí)踐敏捷開(kāi)發(fā)原則模式與實(shí)踐,文章面向?qū)ο笤O(shè)計(jì)的五大原則設(shè)計(jì)模式原則單一職責(zé)原則定義特性僅有一個(gè)引起類(lèi)變化的原因一個(gè)類(lèi)只承擔(dān)一項(xiàng)職責(zé)職責(zé)變化的原因避免相同的職責(zé)分散到不同的類(lèi),功能重復(fù)問(wèn)題一個(gè)類(lèi)承擔(dān)的職責(zé)過(guò)多, PHP設(shè)計(jì)原則梳理,參考《PHP核心技術(shù)與最佳實(shí)踐》、《敏捷開(kāi)發(fā)原則、模式與實(shí)踐》,文章PHP面向?qū)ο笤O(shè)計(jì)的五大原則、設(shè)計(jì)模式原則SOLID 單一...
摘要:它是良好應(yīng)用設(shè)計(jì)的大原則,包含單一責(zé)任原則開(kāi)放封閉原則里氏替換原則接口分離原則依賴倒置原則讓我們通過(guò)代碼示例來(lái)深究下這五個(gè)原則。實(shí)探單一責(zé)任原則代表一個(gè)類(lèi)有且僅有一個(gè)改變的原因,換言之,一個(gè)類(lèi)的職責(zé)范疇是嚴(yán)謹(jǐn)明確的。 聲明:本文并非博主原創(chuàng),而是來(lái)自對(duì)《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當(dāng)然也不是原汁原味的翻譯,能保證90%的原...
閱讀 2571·2021-11-24 09:38
閱讀 2601·2019-08-30 15:54
閱讀 915·2019-08-30 15:52
閱讀 1909·2019-08-30 15:44
閱讀 2713·2019-08-30 13:48
閱讀 768·2019-08-29 16:21
閱讀 996·2019-08-29 14:03
閱讀 2212·2019-08-28 18:15