S.O.L.I.D原則的重要性
> S.O.L.I.D principles
介紹
作為開發(fā)人員,我們一直在處理遺留代碼庫。 大多數(shù)傳統(tǒng)代碼庫具有緊密耦合的類,冗余代碼和較少的測試范圍。 快速瀏覽代碼時(shí),這會使開發(fā)人員難以理解代碼庫的功能。
想象一下在類中遍歷無數(shù)行代碼以僅修復(fù)錯(cuò)誤的痛苦。 開發(fā)人員最終可能會閱讀更多的代碼行,而不是編寫更多的代碼行。 此外,固定一個(gè)流程可能會導(dǎo)致另一個(gè)流程中斷。 這使我想起以下著名的模因。
> Fix one bug & you have 10 more ready
由于在舊版軟件中沒有進(jìn)行積極的開發(fā),因此使開發(fā)人員和管理人員陷入了困境。 然后,團(tuán)隊(duì)考慮重新編寫整個(gè)服務(wù)并棄用舊的服務(wù)。
為什么采用軟件設(shè)計(jì)原則?
在當(dāng)今不斷發(fā)展的世界中,客戶需求以前所未有的速度不斷變化。 對于軟件團(tuán)隊(duì)來說,適應(yīng)新的需求并迅速發(fā)布變更變得至關(guān)重要。 為此,必須減少軟件開發(fā)和測試時(shí)間。
同時(shí),每隔一年就會引入新技術(shù)。 通常,通過替換現(xiàn)有技術(shù)來嘗試更優(yōu)化,更高效的技術(shù)。 因此,編寫的代碼必須靈活且松散地耦合以引入任何更改。
編寫良好的代碼很容易掌握。 新開發(fā)人員不必花更多的時(shí)間來閱讀代碼,而只需修改部分代碼即可。 維護(hù)良好的軟件可以提高開發(fā)人員和團(tuán)隊(duì)的生產(chǎn)力。 此外,高測試覆蓋率增加了部署新變更的信心。
S.O.L.I.D是Michael Feathers創(chuàng)造的首字母縮寫詞,也是Robert Martin(鮑勃叔叔)發(fā)表的原理的子集。 我們將介紹這五個(gè)原則,并為每個(gè)原則進(jìn)行說明。
S-單一責(zé)任原則
這是最簡單的原理之一。 它指出"一個(gè)班級只有一個(gè)改變的理由"。 很多時(shí)候,您可能會發(fā)現(xiàn)一類執(zhí)行的功能比預(yù)期的要多。
假設(shè)您正在編寫銀行軟件的代碼。 該功能是顯示給定用戶的聲明。 該代碼從數(shù)據(jù)庫中獲取數(shù)據(jù),并以用戶選擇的格式顯示數(shù)據(jù)。 您最終編寫了以下代碼。
> BankStatementManager
從上面的代碼片段中可以看到," BankStatementMgr"類一次執(zhí)行多項(xiàng)操作。 它正在從數(shù)據(jù)庫中獲取數(shù)據(jù),解析結(jié)果,然后以用戶指定的格式顯示它。 您可以觀察到以下缺陷:
· 沒有責(zé)任隔離。 如果引入了新格式或添加了新的數(shù)據(jù)庫列,則該類將需要更改
· 該類與數(shù)據(jù)庫驅(qū)動(dòng)程序緊密耦合。 數(shù)據(jù)庫驅(qū)動(dòng)程序或SQL查詢中的任何更改都將導(dǎo)致對該類的修改
· 交易格式無法單獨(dú)測試,因?yàn)锽ankStatementMgr不會公開交易格式
· 該代碼不是模塊化的,因?yàn)槎鄠€(gè)功能相互交織
以上缺點(diǎn)可以通過以下方法克服:
· 定義一個(gè)單獨(dú)的格式化程序,其職責(zé)是格式化事務(wù)
· 添加一個(gè)數(shù)據(jù)庫訪問對象或DAO,它將封裝數(shù)據(jù)庫驅(qū)動(dòng)程序并完成所有查詢?nèi)蝿?wù)
· BankStatementMgr會將請求委托給DAO以獲取數(shù)據(jù),然后將響應(yīng)傳遞給格式化程序以進(jìn)行美化
· 通過這種方式,我們可以隔離地測試DAO和Formatter并實(shí)現(xiàn)松散耦合。 因此,它將通過分離職責(zé)來使代碼模塊化
以下是我們的修改代碼:
> BankStatementManager
> StatementFormatter
> TransactionDAO
仍有很多改進(jìn)的余地,我們將在下一部分中看到如何重構(gòu)和使事情變得更好。
O — 開閉原理
該原則指出,代碼應(yīng)打開以進(jìn)行擴(kuò)展,并應(yīng)關(guān)閉以進(jìn)行修改。 如果需要添加新功能,則必須擴(kuò)展該類。 此外,為使系統(tǒng)可擴(kuò)展,應(yīng)將其行為分開。
我們將通過一個(gè)例子來理解這一點(diǎn)。 假設(shè)您是接受不同方式付款的電子商務(wù)商人。 您已經(jīng)集成了Paypal,Wepay,google Pay等不同的模式,并開發(fā)了付款處理器。 您提出以下代碼。
> PaymentProcessor
> PaymentHandler
PaymentHandler處理付款請求。 PaymentProcessor確定模式并將其委托給正確的操作。 此代碼違反了開閉原則,因?yàn)槿魏喂δ芏夹枰赑aymentProcessor和PaymentHandler中進(jìn)行修改。 此設(shè)計(jì)不可擴(kuò)展,因?yàn)槊糠N新的付款方式都會在switch語句中引入一個(gè)新的case塊。
為了使代碼可擴(kuò)展,我們可以使PaymentHandler抽象,并定義一種處理Payment的方法。 為了處理新的付款方式,我們可以擴(kuò)展此基類并覆蓋其handlePayment方法。 下面是新代碼。
> abstract PaymentHandler
> GooglePayHandler
> CardPaymentHandler
現(xiàn)在,我們將創(chuàng)建一個(gè)工廠類,該類將負(fù)責(zé)存儲特定的處理程序并根據(jù)模式返回它。
> PaymentHandlerFactory
> PaymentProcessor
我們的新代碼現(xiàn)在符合開閉原則。 要添加新的行為,我們只需要擴(kuò)展抽象類PaymentHandler并在工廠中對其進(jìn)行配置。 無需修改PaymentProcessor。
L-Liskov替代原理
乍一看,這個(gè)名字聽起來很嚇人。 該原理指出,同一超類的對象應(yīng)該能夠彼此替代而不破壞現(xiàn)有代碼。
我們將以為電影開發(fā)剪貼簿為例。 抓取工具提供了一個(gè)界面,可以按電影名稱或演員搜索電影。
> MovieSearch
> IMDB Search
> Rotten Tomatoes Search
> Client code using the MovieSearch interface
我們有兩種不同的實(shí)現(xiàn)。 一個(gè)用于爛番茄,另一個(gè)用于IMDB。 它們都是可替換的,并且可以使用相同的接口進(jìn)行訪問。
如果未實(shí)現(xiàn)派生類中的方法,則會違反該原理。 以下是違反Liskov原理的示例。
> AllMoviesSearch
在這種情況下,我們無法將其他派生類(例如IMDB和爛番茄)替換為"所有電影"。 該方法未實(shí)現(xiàn)searchByMovieName方法,并且不會在客戶端代碼中導(dǎo)致一致的行為。
I —接口隔離
根據(jù)此原則,不應(yīng)讓客戶實(shí)施不需要的方法。 如果您定義了客戶端不使用的方法,則接口將變得過于龐大且受到污染。
如果某個(gè)接口因混合功能而變得太大,則可以將其隔離為多個(gè)較小的接口。 讓我們看一下投資組合服務(wù)的示例,該服務(wù)允許客戶訂購股票,ETF,期權(quán)等
> Interface Portfolio
我們定義了一個(gè)接口Portfolio,該接口允許客戶訂購股票,ETF,以及兩者的組合。
> ETFOrderService
> StockOrderService
如果我們決定在訂購股票時(shí)增加價(jià)格作為參數(shù)怎么辦? 它將需要更改orderStocks方法以接受價(jià)格作為參數(shù)。 此外,此更改必須由ETFOrderService合并,即使它不支持orderStocks方法。
為了克服這個(gè)問題,我們可以將接口分成兩個(gè)部分-a)StockPortfolio b)ETFPortfolio
> StockPortfolio
> ETFPortfolio
使用新的接口,StockOrderService無需處理訂購ETF。 這同樣適用于ETFOrderService。
> ETFOrderService
> StockOrderService
界面隔離與"單一責(zé)任"和" Liskov替代原則"有一些相似之處。
在上面帶有龐大接口的示例中,我們在StockOrderService中引發(fā)了Exception。 這違反了《里斯科夫換人原則》。 在這種情況下,派生類不會擴(kuò)展功能。
如果在接口中定義了不相關(guān)的方法,則該類將有多個(gè)更改原因。 這違反了單一責(zé)任原則。
D —依賴倒置
根據(jù)依賴倒置,程序中的高級模塊一定不能與低級模塊緊密耦合。 兩個(gè)模塊都必須依賴抽象。 該原理提供了一種構(gòu)建松耦合軟件模塊的機(jī)制。
讓我們看看以下示例。 在此示例中,類OrderHistory從PostgreSQL數(shù)據(jù)存儲中獲取數(shù)據(jù)。
> OrderHistory
OrderHistory類必須知道PostgresDB依賴項(xiàng)的實(shí)現(xiàn)細(xì)節(jié)。 如果我們決定使用其他數(shù)據(jù)庫驅(qū)動(dòng)程序,則需要用新的依賴項(xiàng)替換所有PostgresDB實(shí)例。
此外,DB驅(qū)動(dòng)程序更改的一項(xiàng)功能是什么? 它還需要在OrderHistory類中進(jìn)行更改,以調(diào)用數(shù)據(jù)庫驅(qū)動(dòng)程序的方法。
可以通過聲明接口DataStore除去此耦合。 此接口將公開使用者將調(diào)用的API。 我們可以有DataStore的多種實(shí)現(xiàn)-a)Postgres DataStore b)MySQL DataStore c)S3,等等
> DataStore
> PostgresDataStore
> OrderHistory
我們的消費(fèi)者類別現(xiàn)在不必處理正在使用什么數(shù)據(jù)存儲的底層細(xì)節(jié)。 高級模塊OrderHistory依賴于接口DataStore來訪問數(shù)據(jù)。 較低級別的DataStore實(shí)現(xiàn)的任何更改都不會對OrderHistory產(chǎn)生任何影響。
此外,由于模塊松散耦合,因此可以獨(dú)立測試它們。 使用依賴注入,可以輕松地將新的實(shí)現(xiàn)注入到高級模塊中。
結(jié)論
以上五項(xiàng)原則構(gòu)成了軟件工程遵循的最佳實(shí)踐的基石。 在日常工作中實(shí)踐上述原理有助于提高軟件的可讀性,模塊化,可擴(kuò)展性和可測試性。
最終,它有助于構(gòu)建易于維護(hù)的,易于維護(hù)的軟件。 遵循上述做法有助于提高開發(fā)人員的生產(chǎn)率和工程團(tuán)隊(duì)的敏捷性。
參考資料
· Android中的扎實(shí)原則
· 簡化基本原則
· 里斯科夫替代原則
· 通過應(yīng)用SOLID原則成為更好的開發(fā)人員
· 五分鐘的SOLID原理
· 封面照片
編碼面試題 Skilled.dev
一個(gè)完整的平臺,在該平臺上,我可以教您找到下一份工作所需的一切,以及可以實(shí)現(xiàn)以下目標(biāo)的技術(shù):
(本文翻譯自Viktors Telle的文章《SOLID Principles — Simplified with Illustrations》,參考:https://levelup.gitconnected.com/solid-principles-simplified-with-illustrations-fe5265f68ec6)