<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          設(shè)計模式:面向?qū)ο蟮脑O(shè)計原則上(SRP、OCP、LSP)

          共 3653字,需瀏覽 8分鐘

           ·

          2021-12-09 00:11

          2bf137b56712787e7cadd8b038156693.webp

          在面向?qū)ο蟮氖澜缋铮梢苑譃椋好嫦驅(qū)ο蟮幕A(chǔ)知識、面向?qū)ο蟮脑O(shè)計原則和設(shè)計模式,如果用武俠小說來做比喻,基礎(chǔ)知識就是需要練習(xí)的基本功、設(shè)計原則就是內(nèi)功心法、設(shè)計模式則是各種各樣的具體招式,所以說熟練掌握了設(shè)計原則,就能以不變應(yīng)萬變。

          面向?qū)ο蟮脑O(shè)計原則,我們最熟悉的就是 SOLID 原則,SOLID 原則是五個常用原則的首字母縮寫,當(dāng)然除了 SOLID 原則,還有一些其他的原則,所以后面就分為 SOLID 原則和其他原則兩大塊來介紹。

          SOLID 原則指的是常用的五個設(shè)計原則:

          • 單一職責(zé)原則(SRP)
          • 開放封閉原則(OCP)
          • 里氏替換原則(LSP)
          • 接口隔離原則(ISP)
          • 依賴倒置原則(DIP)

          我們平時寫代碼會根據(jù)實際的業(yè)務(wù)情況創(chuàng)建類和方法,然后在方法中進(jìn)行邏輯的編寫,SOLID 原則就是告訴我們應(yīng)該怎么合理地組織類和方法。最終使我們開發(fā)的程序能夠滿足:

          • 可擴(kuò)展
          • 可復(fù)用
          • 可閱讀

          這五個原則 Robert C. Martin ?在《敏捷軟件開發(fā):原則、模式與實踐》和《架構(gòu)整潔之道》中都有完整地闡述,恰好,這兩本書我都有。

          03dd7cf6800635149ada757b7359a973.webp

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

          在面試時當(dāng)問起單一職責(zé)原則時,很多同學(xué)都會回答,一個類或方法只做一件事,好像是對的,但也不全對。Robert C. Martin ?在《敏捷軟件開發(fā):原則、模式與實踐》給出的定義是「一個類應(yīng)該只有一個發(fā)生變化的原因」,而到了 《架構(gòu)整潔之道》定義變成了「任何一個軟件模塊應(yīng)該只對某一類行為者負(fù)責(zé)」。

          現(xiàn)在就有三種定義了:

          • 只做一件事:是從內(nèi)容的維度考慮,而不是變化的維度,一件事的這個事可大可小,如果是一個復(fù)雜的系統(tǒng),也會產(chǎn)生出超級類。準(zhǔn)確地說,這個不算是單一職責(zé)原則;
          • 只有一個發(fā)生變化的原因:軟件是在不斷迭代的,不可能不發(fā)生變化,常常一個類在頻繁地進(jìn)行修改,原因就是不止一個變化的原因,所以讓類只有一個發(fā)生變化的原因,可以讓類更加內(nèi)聚,但極端情況下,我們進(jìn)行細(xì)粒度化地拆解,每個類可能只有一個方法了,這也不是想要的結(jié)果;
          • 只對某一類行為者負(fù)責(zé):該定義除了變化,更是考慮了變化的來源,變化的來源就是平時提需求的人,這些人有著不同的職責(zé)和角色,按照這個維度,將不同的角色的人關(guān)注的內(nèi)容劃分到不同的地方,類的劃分會更加合理。

          舉個例子:低代碼平臺中的表單模型,有下面一些場景:

          • 前臺表單打開時的渲染;
          • 前臺表單數(shù)據(jù)的收集和存儲;
          • 后端表單布局的設(shè)置;
          • 后端表單屬性的設(shè)置;
          • 后端表單中控件屬性的設(shè)置;
          • 后端表單拖入控件后根據(jù)數(shù)據(jù)模型的對接。

          如果按照只做一件事的定義,這些場景都可以放在一個類中,因為都是跟表單相關(guān)的一件事,隨著功能的進(jìn)化,表單相關(guān)的功能會越來越多,這個類也就會越來越龐大。

          如果按照只有一個發(fā)生變化的原因的定義,上面列舉的場景會拆分成獨立的類,也有可能顆粒度更細(xì),就容易變成過度設(shè)計了,導(dǎo)致復(fù)雜度變高。

          最后一種,按照變化來源的維度,表單可以分為普通用戶的前臺使用和管理員進(jìn)行表單模型設(shè)置兩種角色。按這兩種角色進(jìn)行拆分,如果想要讓表單的布局設(shè)置變得更易用,需要調(diào)整代碼,就不會影響到前臺用戶的相關(guān)功能。

          單一職責(zé)既指導(dǎo)我們怎么進(jìn)行代碼的封裝,將什么內(nèi)容的代碼放到一起,又告訴我們需要識別代碼變化的來源,怎樣將揉在一起的代碼進(jìn)行合理地分解。

          開放封閉原則(OCP)

          只要我們的產(chǎn)品在進(jìn)行迭代,就存在代碼的添加和修改。只要存在代碼的修改,就會帶來風(fēng)險,OCP 原則讓他們盡量保持穩(wěn)定的部分的不變,如果需要添加新的功能就使用擴(kuò)展的方式進(jìn)行實現(xiàn)。該原則的定義是:軟件實體(類、模塊、函數(shù))應(yīng)該對擴(kuò)展開放,對修改封閉。

          在日常開發(fā)中,經(jīng)常會有這樣的情況:

          • 一個很小的改動,預(yù)估半天就能完成,開發(fā)做著做著說時間不夠,關(guān)聯(lián)的地方太多了,最終兩三天才能完成;
          • 一個很小的改動,開發(fā)很快就調(diào)整完了,在驗證時發(fā)現(xiàn)其他很多不相干的地方出現(xiàn)各種問題。

          究其原因,就是代碼耦合性高,一個很小的代碼改動會產(chǎn)生連鎖反應(yīng),擴(kuò)展性差,OCP 原則就是解決擴(kuò)展性問題的。

          舉個例子:在低代碼產(chǎn)品的列表模型有兩個關(guān)鍵點,數(shù)據(jù)源和展現(xiàn)模式,起初,數(shù)據(jù)源就是數(shù)據(jù)庫中的表,展示模式就是普通的表格,慢慢地列表模型會不斷地豐富:

          • 數(shù)據(jù)源:表、視圖、存儲過程、API 接口等;
          • 展現(xiàn)模式:表格、樹、日歷、時間軸等。

          如果代碼都寫到一起,當(dāng)出現(xiàn)這些新增需求的時候,就需要修改原來的代碼:

          • 添加很多的 if 判斷;
          • 在方法中添加新的參數(shù)用來進(jìn)行一些場景的判斷;
          • 為了不影響上層的調(diào)用,方法的參數(shù)設(shè)置成了可空,很容易導(dǎo)致后續(xù)開發(fā)人員在調(diào)用時的誤用。

          使用 OCP 原則來看上面的例子,定義好數(shù)據(jù)輸出的格式和接口抽象,就不用關(guān)心背后的源是什么,有任何的新的類型的添加,只需要擴(kuò)展一個新的類進(jìn)行相關(guān)邏輯的實現(xiàn)即可。

          像我們熟悉的 VS Code 編輯器,只要符合接口標(biāo)準(zhǔn),就能夠開發(fā)出各種各樣的插件,這就是典型的面向擴(kuò)展性的設(shè)計,符合 OCP 原則。

          如果是單一職責(zé)原則的主要邏輯是封裝,那開放封閉原則的主要邏輯則是抽象(繼承)和多態(tài)。

          里氏替換原則(LSP)

          我們只要談及面向接口編程,就會涉及到繼承,繼承中的子類不是隨便怎么寫都可以,而是要遵循一定的原則,這就是里氏替換原則發(fā)揮作用的地方。

          1988 年,Barbara Liskov 在描述如何定義子類型時寫了這樣一段話:

          這里需要的是一種可替換性:如果對于每個類型是 S 的對象 o1 都存在一個類型為 T 的對象 o2 ,能使操作 T 類型的程序 P 在用 o2 替換 o1 時行為保持不變,我們就可以將 S 稱為 T 的子類型。

          簡單的定義就是:子類型必須能夠替換掉他們的基類型。

          下面拿書中的正方形和長方形的例子,可以很好的說明如果違反 LSP 后果會很嚴(yán)重。

          按照我們的常識,正方形是一種特殊的長方形,所以正方形的類繼承長方形的類就理所當(dāng)然了:

          public?class?Rectangle
          {
          ????protected?int?_height;
          ????protected?int?_width;

          ????public?virtual?void?SetHeight(int?height)
          ????{
          ????????this._height?=?height;
          ????}
          ????public?virtual?void?SetWidth(int?width)
          ????{
          ????????this._width?=?width;
          ????}
          ????public?int?Area()
          ????{
          ????????return?_height?*?_width;
          ????}
          }

          public?class?Square:Rectangle
          {
          ????private?void?SetSide(int?side)
          ????{
          ????????this._height?=?side;
          ????????this._width?=?side;
          ????}

          ????public?override?void?SetHeight(int?height)
          ????{
          ????????SetSide(height);
          ????}
          ????public?override?void?SetWidth(int?width)
          ????{
          ????????SetSide(width);
          ????}
          }

          按照里氏替換的原則,子類要能夠替換父類,所以應(yīng)該要能夠支持下面這種調(diào)用:

          Rectangle?rectangle?=?new?Square();
          rectangle.SetHeight(5);
          rectangle.SetWidth(4);
          int?area?=?rectangle.Area();
          if?(area?!=?20)
          {
          ????throw?new?Exception("長和寬相乘和面積不相等");
          }
          Console.WriteLine(area);
          Console.ReadLine();

          上面的代碼,當(dāng) new 后面用子類 Square 替換了 Rectangle 后,area 的值就不是 20 了,所以是違反里氏替換原則的。雖然我們直覺上感覺正方形是一種特殊的長方形,但從代碼邏輯的角度來看,正方形和長方形并不是 IS-A 的關(guān)系,而 ?IS-A 的關(guān)系是繼承時需要遵循的規(guī)則?。

          IS-A 是指當(dāng) A 是 B 的子類,就需要滿足 A 是一個 B,判斷 A 是不是一個 B 可以根據(jù)所表現(xiàn)出來的行為,例如將鳥作為一個抽象,里面只有一個行為吃,那么貓、狗、魚都可以作為其子類,如果定義的行為只有飛,那么鴕鳥也不能作為其子類。所以說只有行為相同,才是符合 IS-A 關(guān)系,也就不會違反 LSP 原則。

          LSP 原則用來指導(dǎo)繼承關(guān)系中子類該如何設(shè)計,子類的設(shè)計要保證在替換父類的時候,不改變原有程序的邏輯以及不破壞原有程序的正確性。

          由于篇幅的原因,下一篇再介紹接口隔離原則(ISP)和依賴倒置原則(DIP)。希望本文對您有所幫助。



          瀏覽 78
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久艹伊人av | 激情久久婷婷 | 51成人做爰www免费看网站 | 久久九九免费视频 | 黄片网站在线看 |