2019-07-21 淺談 SOLID 原則的具體使用【轉(zhuǎn)】

閱讀目錄

SOLID 是面向?qū)ο笤O(shè)計5大重要原則的首字母縮寫,當(dāng)我們設(shè)計類和模塊時裆赵,遵守 SOLID 原則可以讓軟件更加健壯和穩(wěn)定。那么浴麻,什么是 SOLID 原則呢?本篇文章我將談?wù)?SOLID 原則在軟件開發(fā)中的具體使用囤攀。

回到頂部

單一職責(zé)原則(SRP)

單一職責(zé)原則(SRP)表明一個類有且只有一個職責(zé)软免。一個類就像容器一樣,它能添加任意數(shù)量的屬性焚挠、方法等膏萧。然而,如果你試圖讓一個類實現(xiàn)太多宣蔚,很快這個類就會變得笨重向抢。任意小的改變都將導(dǎo)致這個單一類的變化认境。當(dāng)你改了這個類胚委,你將需要重新測試一遍。如果你遵守 SRP叉信,你的類將變得簡潔和靈活亩冬。每一個類將負責(zé)單一的問題、任務(wù)或者它關(guān)注的點硼身,這種方式你只需要改變相應(yīng)的類硅急,只有這個類需要再次測試。SRP 核心是把整個問題分為小部分佳遂,并且每個小部分都將通過一個單獨的類負責(zé)营袜。

假設(shè)你在構(gòu)建一個應(yīng)用程序,其中有個模塊是根據(jù)條件搜索顧客并以Excel形式導(dǎo)出丑罪。隨著業(yè)務(wù)的發(fā)展荚板,搜索條件會不斷增加,導(dǎo)出數(shù)據(jù)的分類也會不斷增加吩屹。如果此時將搜索與數(shù)據(jù)導(dǎo)出功能放在同一個類中跪另,勢必會變的笨重起來,即使是微小的改動煤搜,也可能影響其他功能免绿。所以根據(jù)單一職責(zé)原則,一個類只有一個職責(zé)擦盾,故創(chuàng)建兩個單獨的類嘲驾,分別處理搜索以及導(dǎo)出數(shù)據(jù)淌哟。

image

回到頂部

開放封閉原則(OCP)

開放封閉原則(OCP)指出,一個類應(yīng)該對擴展開放辽故,對修改關(guān)閉绞绒。這意味一旦你創(chuàng)建了一個類并且應(yīng)用程序的其他部分開始使用它,你不應(yīng)該修改它榕暇。為什么呢蓬衡?因為如果你改變它,很可能你的改變會引發(fā)系統(tǒng)的崩潰彤枢。如果你需要一些額外功能狰晚,你應(yīng)該擴展這個類而不是修改它。使用這種方式缴啡,現(xiàn)有系統(tǒng)不會看到任何新變化的影響壁晒。同時,你只需要測試新創(chuàng)建的類业栅。

假設(shè)你現(xiàn)在正在開發(fā)一個 Web 應(yīng)用程序秒咐,包括一個在線納稅計算器。用戶可以訪問Web 頁面,指定他們的收入和費用的細節(jié),并使用一些數(shù)學(xué)公式來計算應(yīng)納稅額碘裕⌒。考慮到這一點,你創(chuàng)建了如下類:

public class TaxCalculator
{
    public decimal Calculate(decimal income, decimal deduction, string country)
    {
        decimal taxAmount = 0;
        decimal taxableIncome = income - deduction;
        switch (country)
        {
            case "India":
                //Todo calculation
                break;
            case "USA":
                //Todo calculation 
                break;
            case "UK":
                //Todocalculation
                break;
        }
        return taxAmount;
    }
}

這個方法非常簡單帮孔,通過指定收入和支出雷滋,可以動態(tài)切換不同的國家計算不同的納稅額。但這里隱含了一個問題文兢,它只考慮了3個國家晤斩。當(dāng)這個 Web 應(yīng)用變得越來越流行時,越來越多的國家將被加進來姆坚,你不得不去修改 Calculate 方法澳泵。這違反了開放封閉原則,有可能你的修改會導(dǎo)致系統(tǒng)其他模塊的崩潰兼呵。

讓我們對這個功能進行重構(gòu)兔辅,以符合對擴展是開放,對修改是封閉的萍程。

image

根據(jù)類圖幢妄,可以看到通過繼承實現(xiàn)橫向的擴展,并且不會引發(fā)對其他不相關(guān)類的修改茫负。這時 TaxCalculator 類中的 Calculate 方法會異常簡單:

public decimal Calculate(CountryTaxCalculator obj)
{
    decimal taxAmount = 0;
    taxAmount = obj.CalculateTaxAmount();
    return taxAmount;
}

回到頂部

里氏替換原則(LSP)

里氏替換原則指出蕉鸳,派生的子類應(yīng)該是可替換基類的,也就是說任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)潮尝。值得注意的是榕吼,當(dāng)你通過繼承實現(xiàn)多態(tài)行為時,如果派生類沒有遵守LSP勉失,可能會讓系統(tǒng)引發(fā)異常羹蚣。所以請謹慎使用繼承,只有確定是“is-a”的關(guān)系時才使用繼承乱凿。

假設(shè)你在開發(fā)一個大的門戶網(wǎng)站顽素,并提供很多定制的功能給終端用戶,根據(jù)用戶的級別徒蟆,系統(tǒng)提供了不同級別的設(shè)定胁出。考慮到這個需求段审,設(shè)計如下類圖:

image

可以看到全蝶,ISettings 接口有 GlobalSettings、SectionSettings 以及 UserSettings 三個不同的實現(xiàn)寺枉。GlobalSettings 設(shè)置會影響整個應(yīng)用程序,例如標(biāo)題抑淫、主題等。SectionSettings 適用于門戶的各個部分,如新聞姥闪、天氣始苇、體育等設(shè)置。UserSettings 為特定登錄用戶設(shè)置,如電子郵件和通知偏好甘畅。

這樣的設(shè)計沒問題埂蕊,但如果有另一個需求往弓,系統(tǒng)需要支持游客訪問疏唾,唯一區(qū)別是游客不支持系統(tǒng)的設(shè)定,為了滿足這個需求函似,你可能會如下設(shè)計:

public class GuestSettings : ISettings
{
    public void GetSettings()
    {
        //get settings from database
        //include guest name槐脏、ip address...
    }

    public void SetSettings()
    {
        //guests are not allowed set settings
        throw new NotImplementedException();
    }
}

這樣沒問題嗎?準確來說撇寞,系統(tǒng)存在隱患顿天。當(dāng)單獨使用 GuestSettings 時,因為我們了解游客不能設(shè)置蔑担,所以我們潛意識并不會主動調(diào)用 SetSettings 方法牌废。但是由于多態(tài),ISettings 接口的實現(xiàn)可以被替換為 GuestSettings 對象啤握,當(dāng)調(diào)用SetSettings 方法時鸟缕,可能會引發(fā)系統(tǒng)異常。

重構(gòu)這個功能,拆分為兩個不同的接口:IReadableSettings 和 IWritableSettings懂从。子類根據(jù)需求實現(xiàn)所需的接口授段。

image

回到頂部

接口隔離原則(ISP)

接口隔離原則(ISP)表明類不應(yīng)該被迫依賴他們不使用的方法,也就是說一個接口應(yīng)該擁有盡可能少的行為番甩,它是精簡的侵贵,也是單一的。

假設(shè)你正在開發(fā)一個電子商務(wù)的網(wǎng)站,需要有一個購物車和關(guān)聯(lián)訂單處理機制缘薛。你設(shè)計一個接口 IOrderProcessor,它用包含一個驗證信用卡是否有效的方法(ValidateCardInfo)以及收件人地址是否有效的方法(ValidateShippingAddress)窍育。與此同時,創(chuàng)建一個OnlineOrderProcessor 的類表示在線支付宴胧。

這非常好蔫骂,你的網(wǎng)站也能正常工作。現(xiàn)在讓我們來考慮另一種情形牺汤,假設(shè)在線信用卡支付不再有效辽旋,公司決定接受貨到付款支付。
乍一看,這個解決方案聽起來很簡單,你可以創(chuàng)建一個CashOnDeliveryProcessor 并實現(xiàn) IOrderProcessor 接口檐迟。貨到付款的購買方式不會涉及任何信貸卡驗證,所以补胚,CashOnDeliveryOrderProcessor 類內(nèi)部的 ValidateCardInfo 方法拋出 NotImplementedException。

image

這樣的設(shè)計在未來可能會出現(xiàn)的潛在問題追迟。假設(shè)由于某種原因在線信用用卡付款需要額外的驗證步驟溶其。自然,IOrderProcessor 將被修改,它將包括那些額外的方法,于此同時 OnlineOrderProcessor 將實現(xiàn)這些額外的方法敦间。然而,CashOnDeliveryOrderProcessor 盡管不需要任何的附加功能,但你必須實現(xiàn)這些附加的功能瓶逃。顯然,這違反了接口隔離原則廓块。

你需要將這個功能重構(gòu):

image

新的設(shè)計分成兩個接口厢绝。IOrderProcessor 接口只包含兩個方法:ValidateShippingAddress 和 ProcessOrder,而 ValidateCardInfo 抽象到到一個單獨的接口:IOnlineOrderProcessor〈铮現(xiàn)在,在線信用卡支付的任何改變只局限于IOnlineOrderProcessor 和它的子類實現(xiàn)昔汉,而 CashOnDeliveryOrderProcessor 是不會被影響。因此,新設(shè)計符合接口隔離原則拴清。

回到頂部

依賴倒置原則(DIP)

依賴倒置原則(DIP)表明高層模塊不應(yīng)該依賴低層模塊靶病,相反,他們應(yīng)該依賴抽象類或者接口口予。這意味著你不應(yīng)該在高層模塊中使用具體的低層模塊娄周。因為這樣的話,高層模塊變得緊耦合低層模塊沪停。如果明天煤辨,你改變了低層模塊,那么高層模塊也會被修改。根據(jù)DIP原則掷酗,高層模塊應(yīng)該依賴抽象(以抽象類或者接口的形式)调违,低層模塊也是如此。通過面向接口(抽象類)編程泻轰,緊耦合被移除技肩。

那么什么是高層模塊,什么是低層模塊呢浮声?通常情況下虚婿,我們會在一個類(高層模塊)的內(nèi)部實例化它依賴的對象(低層模塊),這樣勢必造成兩者的緊耦合泳挥,任何依賴對象的改變都將引起類的改變然痊。

依賴倒置原則表明高層模塊、低層模塊都依賴于抽象屉符,舉個例子剧浸,你現(xiàn)在正在開發(fā)一個通知系統(tǒng),當(dāng)用戶改變密碼時矗钟,郵件通知用戶唆香。

public class UserManager
{

    public void ChangePassword(string username,string oldpwd,string newpwd)
    {
        EmailNotifier notifier = new EmailNotifier();

        //add some logic and change password 
        //Notify the user
        notifier.Notify("Password was changed on "+DateTime.Now);
    }
}

這樣的實現(xiàn)在功能上沒有問題,但試想一下吨艇,新的需求希望通過SNS形式通知用戶躬它,那么我們只能手動將EmaiNorifier 替換為 SNSNotifier。在這兒东涡,UserManager 就是高層模塊冯吓,而EmailNotifier 就是低層模塊,他們彼此耦合疮跑。我們希望解耦组贺,依賴于抽象 INotifier,也就是面向接口的編程祸挪。

image

回到頂部

小結(jié)

本篇博客為大家介紹了面向?qū)ο笤O(shè)計的 SOLID 原則锣披,并以具體的案例輔助講解。你可以看到贿条,繼承和多態(tài)在SOLID 原則中扮演了非常重要的角色。我們的應(yīng)用程序不能過度設(shè)計增热,當(dāng)然也不能隨意設(shè)計整以。了解基本的 SOLID 原則能讓你的應(yīng)用程序變得健壯。你可以在Github 上查看具體的示例代碼:https://github.com/MEyes/SOLID.Principles

<fieldset style="margin: 0px; padding: 0px; background-color: rgb(255, 255, 160); font-family: 微軟雅黑; font-size: 15px; color: black; width: 650px;"><legend style="margin: 0px; padding: 0px;">[圖片上傳失敗...(image-7b353e-1563678086878)]</legend>

本博客為木宛城主原創(chuàng)峻仇,基于Creative Commons Attribution 2.5 China Mainland License發(fā)布公黑,歡迎轉(zhuǎn)載,演繹或用于商業(yè)目的,但是必須保留本文的署名木宛城主(包含鏈接)凡蚜。如您有任何疑問或者授權(quán)方面的協(xié)商人断,請給我留言。

</fieldset>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朝蜘,一起剝皮案震驚了整個濱河市恶迈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谱醇,老刑警劉巖暇仲,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異副渴,居然都是意外死亡奈附,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門煮剧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斥滤,“玉大人,你說我怎么就攤上這事勉盅≈械” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵菇篡,是天一觀的道長漩符。 經(jīng)常有香客問我,道長驱还,這世上最難降的妖魔是什么嗜暴? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮议蟆,結(jié)果婚禮上闷沥,老公的妹妹穿的比我還像新娘。我一直安慰自己咐容,他們只是感情好舆逃,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著戳粒,像睡著了一般路狮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔚约,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天奄妨,我揣著相機與錄音,去河邊找鬼苹祟。 笑死砸抛,一個胖子當(dāng)著我的面吹牛评雌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播直焙,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼景东,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奔誓?” 一聲冷哼從身側(cè)響起斤吐,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丝里,沒想到半個月后曲初,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡杯聚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年臼婆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幌绍。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡颁褂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出傀广,到底是詐尸還是另有隱情颁独,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布伪冰,位于F島的核電站誓酒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贮聂。R本人自食惡果不足惜靠柑,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吓懈。 院中可真熱鬧歼冰,春花似錦、人聲如沸耻警。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甘穿。三九已至腮恩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扒磁,已是汗流浹背庆揪。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妨托,地道東北人缸榛。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像兰伤,于是被迫代替她去往敵國和親内颗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容