【設計模式】 面向對象六大設計原則
面向對象設計的六大原則 : 單一職責原則, 里氏替換原則, 依賴倒置原則, 接口隔離原則, 迪米特法則, 開閉原則;
一. 單一職責原則
1. 單一職責簡介
單一職責定義 : 有且只有一個原因引起類的變化, 一個接口 或者 類 只有一個職責;
單一職責的好處 :
-- 復雜性 : 降低類的復雜性, 對類或接口的職責有清晰明確定義;
-- 可讀性 : 提高可讀性;
-- 維護 : 提高可維護性;
-- 變更風險 : 降低變更引起的風險, 接口改變只影響相應的實現類, 不影響其他類;
2. 單一職責示例
(1) 反面示例
示例要求 : 創建一個繪圖系統
-- 繪圖 : 可以繪制圓形, 矩形;
-- 顯示 : 顯示繪制好的圖形;
UML 圖 : 明顯下圖不符合單一職責原則, 繪制圓形, 矩形, 顯示圓形, 矩形 都集成在了一個接口中, 明顯不符合單一職責原則;
(2) 正面示例
修改一下上面的示例, 我們可以得到下面的 UML 圖 :
-- 說明 : 將 繪制 和 顯示 分別封裝在兩個接口中, 讓一個實現類同時繼承這兩個接口, 此時實現了接口的單一職責;
3. 單一職責實踐
單一職責適用 : 單一職責同時適用于接口, 類, 方法;
-- 接口 : 接口一定要做到單一職責;
-- 類 : 類的單一職責比較難以實現,?盡量做到只有一個原因引起變化;
-- 方法 : 一個方法盡可能做一件事, 能分解就分解, 分解到原子級別;
二. 里氏替換原則
1. 里氏替換簡介
(1) 里氏替換定義
里氏替換 : 所有 引用基類的地方 必須能 透明地使用其子類的對象;
-- 子類替換父類?: 只要 父類出現的地方子類就可以出現, 替換為子類也不會產生任何錯誤, 使用者不需要知道父類還是子類;
(2) 繼承的優缺點
繼承優點 :
-- 代碼共享 : 共享代碼, 子類都擁有父類的方法和屬性, 將 父類的代碼共享給了子類;
-- 重用性 : 提高代碼的重用性, 子類重用父類的代碼;
-- 子父類異同 : 子類形似父類, 異于父類, 父子都不同;
-- 擴展性 : 提高代碼的可擴展性, 子類就可以為所欲為了, 子類可以隨意擴展父類;
-- 開放性 : 提高產品或項目的開放性, 父類隨意擴展, 開放性隨之增加了;
繼承缺點 :
-- 侵入性 : 繼承是侵入性的, 子類 強制繼承 父類的方法和屬性;
-- 靈活性 : 降低代碼的靈活性, 子類必須擁有父類的屬性和方法, 子類收到了父類的約束, 這是從子類的角度講得;
-- 耦合性 : 增強了耦合性, 父類的屬性和方法被修改時, 還需要顧及其子類, 可能會帶來大量的重構, 這是從父類的角度講的;
2. 里氏替換規范含義
(1) 子類完全實現父類方法
替換方法 : 定義一個接口或抽象類, 編碼實現一個子類繼承或實現該接口或抽象類, 調用類直接傳入接口或抽象類;
示例說明 :
-- UML 圖 :
-- Client 場景類 : 在該場景中創建一個 Player 玩家, 然后為 Player 設置槍, 之后開槍 kill;
-- Player 類 : 玩家類, 該類有一個 IGun 抽象類成員變量;
-- IGun : 所有槍的基類, 所有的槍都要繼承該類;
-- 說明 : 在 Player 中 IGun 類型的成員變量都可以使用 IGun 的三個實現類來替代;
(2) 子類個性
里氏替換單向性 : 子類可以有自己的方法和屬性, 里氏替換可以正著用, 使用子類替換子類, 但是反過來不可以, 子類出現的地方, 父類不能使用;
(3) 覆蓋方法參數放大
前置后置條件 : 子類方法中得前置條件必須與超類中被覆寫的前置條件相同或者更寬松;
-- 前置條件 : 關于參數, 輸入的參數必須符合要求, 才會執行, 必須滿足的條件;
-- 后置條件 : 關于返回值, 方法執行完之后, 需要返回一個結果, 這個結果的標準;
重寫與多態 : 如果子類的前置條件與父類相同, 那么是重寫方法, 如果子類更寬松, 那么就是多態, 生成了新的方法;
(4) 覆蓋方法返回值縮小
返回值 : 父類方法返回值類型 F, 子類方法返回值類型 S, 里氏替換原則是 S 范圍必須小于 F;
-- 重寫 : 父類子類參數相同, S 范圍小于 F;
-- 重載 : 父類 子類 方法參數類型或者數量不同, 如果要符合里氏替換要求的話, 子類參數必須 >= 父類參數, 即不能讓子類自己定義的方法被調用;
3. 里氏替換注意點
避免子類個性 : 如果想要使用里氏替換, 盡量避免讓子類擁有自己單獨的成員變量 或者 方法, 如果子類個性多了, 子類父類關系很難調和;
-- 里氏替換缺點 : 將子類當做父類用, 抹殺了子類的個性;
-- 里氏替換優點 : 將子類單獨作為一個業務來使用, 會讓代碼間的耦合關系都復雜, 缺乏類替換標準;
三. 依賴倒置原則
1. 依賴倒置簡介
(1) 依賴倒置定義
模塊與抽象 :
-- 低層模塊 : 不可分割的原子邏輯是低層模塊;
-- 高層模塊 : 原子邏輯組合成高層模塊;
-- 抽象 : 接口或者抽象類, 不能被實例化;
-- 細節 : 實現類, 實現接口或繼承抽象類就是細節, 可以被實例化;
依賴倒置定義 :
-- 模塊依賴 : 高層模塊不應該依賴底層模塊, 兩者都依賴其抽象, 實現類之間不發生依賴關系, 依賴關系通過接口或抽象類產生;
-- 抽象不依賴細節 : 抽象不依賴細節, 接口或抽象類不依賴與實現類;
-- 細節依賴抽象 : 實現類依賴接口或抽象類;
(2) 依賴倒置好處
依賴倒置好處 : 依賴倒置原則可以 減少類之間的耦合, 提高系統穩定性, 降低并發風險, 提高代碼可讀性 和 可維護性;
-- 耦合 :
-- 并發風險 :
-- 可讀性 :
-- 可維護性 :
2. 依賴倒置注入實現
(1) 構造函數依賴對象
注入方法 : 通過 構造函數參數 聲明依賴對象, 即構造函數注入;
(2) Setter 方法依賴對象
注入方法?: 通過 Setter 函數 參數 聲明依賴對象, 即構造函數注入;
(3) 接口注入依賴對象
注入方法?: 在接口方法的參數中聲明依賴對象, 即接口注入;
3. 依賴倒置遵循規則
依賴倒置本質 : 通過 抽象 即 接口或者抽象類, 使 各個類 和 模塊實現彼此獨立, 實現模塊間 松耦合;
(1) 類有抽象
抽象所有的類 : 每個類盡量都有接口或者抽象類, 最好接口和抽象類都有;
(2) 變量類型抽象
變量類型抽象 : 變量的表面類型盡量都定義成抽象類;
-- 注意 : 不是絕對的, 一些工具類, 組件不必定義抽象;
(3) 類派生控制
派生控制 : 任何類不能從具體類派生;
-- 開發階段 : 在開發階段, 從具體類派生類是不允許的, 這樣會增加繼承的層次, 加大后期維護難度, 盡量將繼承層次控制在 2 層以內;
-- 維護階段 : 維護階段可以出現從具體類派生的情況, 這樣更有利于系統的穩定性;
(4) 不要覆蓋方法
不要覆蓋方法 : 盡量不要覆蓋方法, 如果方法在抽象類中已經實現, 子類不要覆蓋;
-- 覆蓋缺點 : 會對系統穩定性產生影響;
(5) 結合里氏替換
里氏替換 : 父類出現的地方子類就能出現,
接口,抽象類,實現類規則 :
-- 接口 : 負責定義 public 屬性和方法, 并聲明與其它對象的依賴關系;
-- 抽象類 : 負責公共構造部分實現;
-- 實現類 : 準確地實現業務邏輯, 適當時候對父類進行細化;
4. 依賴倒置注意點
(1) 依賴正置
依賴正置 :?類之間的依賴是實體類之間的依賴, 即面向現實編程;
-- 例如 : 我開寶馬車, 我 是 人類型, 寶馬車 是車 類型, 依賴倒置就是 人 依賴 車, 依賴正置就是 我 依賴 寶馬車;
(2) 依賴倒置使用場合
依賴倒置使用場景 :
-- 小項目 : 依賴倒置在小項目中得有點很難體現出來, 是否采用依賴倒置原則影響不大;
-- 大項目 : 項目越大, 需求改變越多, 采用依賴倒置原則設計的接口或抽象類 對 實現類的約束, 會大大減少維護成本;
(3) 依賴倒置與其它原則
依賴倒置與開閉原則聯系 : 依賴倒置原則是實現開閉原則的重要途徑, 依賴倒置原則沒有實現, 無法對擴展開放, 對修改關閉;
四. 接口隔離原則
1. 接口隔離原則簡介
(1) 接口分類
接口分類 :
-- 實例接口 : Java 中得一個類, 對一個類型的描述, 例如 Student xiaoming; 其中的 Student 類就是實例接口, 這不是我們這里所關心的;
-- 類接口 : Java 中得 Interface 接口, 這是我們所說的接口隔離原則中得接口;
(2) 接口隔離定義
接口隔離定義 : 建立單一的接口, 功能盡量細化, 不要建立臃腫的接口;
-- 不需要的接口 : 客戶端盡量不依賴其不需要的接口, 客戶端需要什么接口就提供什么接口, 剔除不需要的接口, 對接口進行細化, 保持接口方法最少;
-- 最小接口 : 類間的依賴關系應該建立在最小接口上, 細化接口;
(3) 接口隔離 與 單一職責區別
單一職責 與 接口隔離 區別 :
-- 單一職責 : 注重職責, 注重業務邏輯上得劃分;
-- 接口隔離 : 注重的是接口的方法盡量少;
(4) 模塊 與 接口
模塊與接口 : 每個模塊盡量都提供一個單一接口, 有幾個模塊, 就由幾個接口, 避免建立一個龐大臃腫的接口對應所有的模塊;
2. 接口隔離原則實現
(1) 接口盡量小
拆分接口 : 接口隔離的核心定義, 不出現臃腫的接口;
-- 限制 : 接口小有限度, 不能違反單一職責原則, 不要將一個業務邏輯拆分成兩個接口;
-- 要求 : 根據接口隔離原則拆分接口時, 必須滿足單一職責原則;
(2) 接口高內聚
高內聚 : 提高接口, 類, 模塊的處理能力, 減少對外界交互;
-- 具體方法 : 接口中盡量少公布public 方法, 對外公布的 public 方法越少, 變更的風險就越小, 有利于后期的維護;
(3) 定制服務
定制服務 :
-- 起源 : 系統模塊間的耦合需要有相互訪問的接口, 這里就需要為各個 客戶端 的訪問提供定制的服務接口;
-- 要求 : 只提供訪問者需要的方法, 不需要的就不提供;
(4) 接口隔離限度
接口隔離局限性 :
-- 粒度小 : 接口粒度越小, 系統越靈活, 但是同時使系統結構復雜, 開發難度增加, 降低了系統的可維護性;
-- 粒度大 : 靈活性降低, 無法提供定制服務, 增大項目風險;
3. 原子接口劃分原則
原子接口或類 : 接口隔離原則 即適用于 接口的定義, 也適用于對類的定義, 那么這樣的接口和類就是 原子接口 和 原子類;
(1) 接口模塊一一對應
接口模塊對應關系 : 一個接口只服務于一個子模塊 或 業務邏輯;
(2) 壓縮方法
方法壓縮 : 通過業務邏輯, 壓縮接口中得 public 方法, 減少接口的方法的數量;
(3) 接口修改
修改適配 : 盡量去修改已經污染的接口, 如果變更風險較大, 采用適配器模式進行轉化處理;
五. 迪米特法則
1. 迪米特法則定義
迪米特法則 : 最少知識原則, 一個對象應該對其它對象有最少的了解, 即一個類對自己需要耦合或者調用的類知道的最少;
2. 低耦合要求
(1) 只和朋友交流
只與朋友通信 :
-- 朋友形成 : 一個對象與其它對象有耦合關系, 兩個對象間的耦合使兩個對象成為朋友關系;
-- 朋友定義 : 出現在 類 成員變量 , 方法參數返回值 中的類是朋友, 其它位置的不是朋友, 在方法體內出現的其它類不是朋友;
(2) 朋友距離
朋友間必須保持距離 :
-- 距離太近示例 : 朋友間不能無話不說, 無所不知, 類 A 與 B 耦合, B 將很多方法暴露給 A, 兩個類之間的的耦合關系非常牢固, 這明顯違反設計原則;
-- 保持距離方法 : 將 類 B 暴露給 A 的方法封裝, 暴露的方法越少越好, 類 B 高內聚, 與 A 低耦合;
-- 設計方法 : 一個類的 public 方法越多, 修改時涉及的范圍也就越大, 變更引起的風險也就越大, 在系統設計時要注意, 能用 private 就用private , 能用 protected 就用 protected, 能少用 public 就少用 public, 能加上 final 就加上 final;
(3) 類方法位置確定
原則 : 如果一個方法放在本類, 不增加類間關系, 不對類產生負面影響, 就放在本類中;
3. 迪米特法則注意事項
迪米特法則核心原則 : 類間解耦, 弱耦合, 耦合降低, 復用率提高;
-- 局限性 : 類間的耦合性太低, 會產生大量的中轉或跳轉類, 會導致系統的復雜性提高, 加大維護難度;
六. 開閉原則
1. 開閉原則定義
開閉原則定義 : 軟件的實體 類, 模塊, 函數 應該對擴展開放, 對修改關閉; 即 軟件實體 應該 通過擴展實現變化, 不是通過 修改已有的代碼實現變化;
--?軟件實體 : 軟件產品中得 邏輯 劃分模塊, 抽象 和 類, 方法;
2. 變化
各種變化 :
-- 邏輯變化 : 變化一個邏輯模塊, 其它模塊不改變, 所有的依賴 或 關聯關系都按照相同的邏輯處理;
-- 子模塊變化 : 一個邏輯模塊變化, 會影響其它模塊, 低層次模塊 (原子邏輯模塊) 變化會引起高層次模塊 (原子模塊組合) 的變化, 通過擴展完成變化時, 高層次模塊需要修改;
3. 開閉原則好處
開閉原則好處 :
-- 利于測試 : 如果改變軟件內容, 需要將所有的測試流程都執行一遍, 如 單元測試, 功能測試, 集成測試等, 如果只是擴展, 只單獨測試擴展部分即可;
-- 提高復用性 : 所有邏輯都從原子邏輯組合, 原子邏輯粒度越小, 復用性越大; 這樣避免相同邏輯存在, 修改時需要修改多個此相同邏輯;
-- 提高可維護性 : 維護一個類最好的方式是 擴展一個類, 而不是修改一個類, 如果需要修改需要讀懂源碼才能修改, 擴展的話只需要了解即可, 直接繼承擴展;
面向對象編程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。