設計模式與設計原則簡介(一)

来源:https://www.cnblogs.com/noteless/archive/2018/11/04/9897464.html
-Advertisement-
Play Games

設計模式的本質是為了遵循設計原則,設計模式是設計原則的具體化表現形式,本文對六大設計原則進行了簡單介紹,開閉原則是根本,單一職責,里氏替換,介面隔離,依賴倒置,組合聚合法則以及迪米特法則,對設計模式進行了一個淺淺的介紹,以進一步往後學習設計模式。 ...


  什麼是設計模式?

我們知道對於很多數學問題,經常會有多種不同的解法

而且這其中可能會有一種比較通用簡便高效的方法

我們在遇到類似的問題或者同一性質的問題時,也往往採用這一種通用的解法

將話題轉移到程式設計中來

對於軟體開發人員, 在軟體開發過程中, 面臨的一般問題的解決方案就是設計模式(準確的說是OOP中)

當然,如同數學的解題思路一樣,設計模式並不是公式一樣的存在

設計模式(Design pattern)代表了最佳的實踐

是眾多軟體開發人員經過相當長的一段時間的試驗和錯誤總結出來的寶貴經驗

是解決問題的思路

總之,設計模式是一種思想思想思想


起源

隨著面向對象編程語言的發展,以及軟體開發規模的不斷擴大

編寫良好的OOP程式變得困難,而編寫可復用的OOP程式則更是困難

在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides

四人合著出版了一本名為 Design Patterns - Elements of Reusable Object-Oriented Software(中文譯名:設計模式 - 可復用面向對象軟體的基礎) 的書

該書首次提到了軟體開發中設計模式的概念。

四位作者合稱 GOF(四人幫,全拼 Gang of Four)

這就是設計模式四個字的起源

當然,即使在這本書出版之前,肯定也已經有很多有經驗的OOP程式員已經在使用自己的經驗(設計模式)了

但是這本書將OOP的設計經驗作為設計模式記錄下來

使我們能夠更加簡單方便的復用成功的設計經驗和體繫結構

 

設計原則

"隨著面向對象編程語言的發展,以及軟體開發規模的不斷擴大

編寫良好的OOP程式變得困難,而編寫可復用的OOP程式則更是困難"

設計模式的起源, 正是需要設計模式的根本原因

藉助於設計模式,可以更好地實現代碼的復用,增加可維護性

 

怎麼才能更好地實現代碼復用呢?

面向對象有幾個原則:

根本原則

 

開閉原則(Open Closed Principle,OCP)  一個軟體實體應當對擴展開放,對修改關閉 。 軟體實體應儘量在不修改原有代碼的情況下進行擴展

 

在開閉原則的定義中,軟體實體可以指一個軟體模塊、一個由多個類組成的局部結構或一個獨立的類

不修改已有代碼的基礎上擴展系統的功能的形式,就是符合開閉原則的

開閉原則的關鍵是抽象

比如,一個方法中

if(){

//...

}else if(){

//...

}

如果新增加一個邏輯功能點,則需要增加新的else  或者 else if ,勢必修改了已有代碼

而如果面向抽象的介面或者抽象類進行編程,擴展增加新的功能,只需要傳遞新的子類即可,原有的代碼功能不會有任何的修改

 

再比如

實際項目開發的時候,我們會把一些配置寫入到配置文件中,而不是"硬編碼"到代碼中

修改參數設置的時候,源代碼無需更改,這也是符合開閉原則

開閉原則作為根本原則,並不限定某種具體場景,只要是符合了這一含義,就是符合開閉原則

總之,開閉原則就是別因為新增功能擴展改(老)代碼

 

 

六大原則

開閉原則是根本綱領,它是面向對象設計的終極目標

 

除了根本原則另外還有六大原則 , 則可以看做是開閉原則的實現方法

 

  • 單一職責原則 (Single Responsiblity Principle SRP)
  • 里氏替換原則(Liskov Substitution Principle,LSP)
  • 依賴倒轉原則(Dependency Inversion Principle,DIP)
  • 介面隔離原則(Interface Segregation Principle,ISP)
  • 合成/聚合復用原則(Composite/Aggregate Reuse Principle,C/ARP)
  • 迪米特法則(Principle of Least Knowledge,PLK,也叫最小知識原則)

 

單一職責原則 (Single Responsiblity Principle SRP)

一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因

單一職責的原則很簡單,就是一個實體(一個類或者一個功能模塊)不要承擔過多的責任

承擔了過多的責任也就意味著多個功能的耦合

堆積木時, 到底是一塊積木比較容易利用, 還是多塊積木拼接起來的"一大塊" 更容易利用? 結果顯而易見 

而且,承擔了過多的責任,也就是可能會因為多個原因修改這段代碼

隨之而來的是不穩定性以及維護成本的增加,也就是將會有多個原因引起他變化

單一職責原則的根本在於控制類的粒度大小

 

里氏替換原則(Liskov Substitution Principle,LSP)

里氏替換原則是以提出者 Barbara Liskov  的名字命名的 

定義:

如果對每一個類型為 T1的對象 o1,都有類型為 T2 的對象o2

使得以 T1定義的所有程式 P 在所有的對象 o1 都代換成 o2 時,程式 P 的行為沒有發生變化

那麼類型 T2 是類型 T1 的子類型

 

簡單說就是 如果 一個程式P(T1) ,如果將輸入T1 替換為T2 ,而且 P(T1) = P(T2)

那麼T2 是T1的子類型

 

再簡單的概述就是:

所有引用基類的地方必須能透明地使用其子類的對象

 

透明也就意味著不感知,不受任何影響

聽起來好像很自然的就可以做到

假如子類覆蓋了父類的方法呢?假如子類覆蓋了父類的方法並且改變了父類方法的原有功能邏輯呢?

比如,原來傳遞來兩個參數進行加法運算,子類覆蓋後,進行減法運算,會發生什麼?

里氏代換原則的根本,在軟體中將一個基類對象替換成它的子類對象,程式將不會產生任何錯誤和異常

想要透明的使用子類,滿足里氏替換原則

需要註意應該儘可能的將父類設計為抽象類或者介面

讓子類繼承父類或實現父介面,並實現在父類中聲明的方法,這樣可以做到滿足開閉原則

子類的所有方法必須在父類中聲明,或子類必須實現父類中聲明的所有方法,也就是父類定義,子類實現

而且,子類不應該破壞父類的契約,也就是不能更改原有的方法的邏輯含義

里氏替換是繼承復用的基石,只有當子類可以替換父類,且軟體單位的功能不受到影響時

父類才能真正被覆用,而子類也能夠在基類的基礎上增加新的行為

里氏代換原則是對開閉原則的補充。

實現開閉原則的關鍵步驟就是抽象化,而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範

 

依賴倒轉原則(Dependency Inversion  Principle, DIP)

抽象不應該依賴於細節,細節應當依賴於抽象; 換言之,要針對介面編程,而不是針對實現編程

 

也就是使用介面和抽象類進行變數類型聲明、參數類型聲明、方法返回類型聲明,以及數據類型的轉換等,而不是使用具體的類

在需要時,將具體類的對象通過依賴註入(DependencyInjection, DI)的方式註入到其他對象中

在引入抽象層後,程式中儘量使用抽象層進行編程, 系統將具有很好的靈活性 並且將具體類寫在配置文件中

如果系統行為發生變化,只需要對抽象層進行擴展,並修改配置文件

而無須修改原有系統的源代碼 , 擴展系統的功能無需修改原來的代碼,滿足開閉原則的要求 

 

介面隔離原則(Interface Segregation Principle,ISP)

使用多個專門的介面,而不使用單一的總介面,即客戶端不應該依賴那些它不需要的介面

根據介面隔離原則,當一個介面太大時,我們需要將它分割成一些更細小的介面,使用該介面的客戶端僅需知道與之相關的方法即可

介面隔離根本在於不要強迫客戶端程式依賴他們不需要使用的方法

 

合成/聚合復用原則(Composite/Aggregate Reuse Principle,C/ARP)

復用一個類有兩種常用形式,繼承和組合

儘量使用對象組合,而不是繼承來達到復用的目的,因為繼承子類可以覆蓋父類的方法,將細節暴露給子類

而且會建立強耦合關係,是一種靜態關係,不能再運行時更改等等弊端

個人建議,對於繼承的態度是不濫用,不棄用,帶著腦子用!

 

迪米特法則(Principle of Least Knowledge,PLK,也叫最小知識原則)

一個軟體實體應當儘可能少地與其他實體發生相互作用

也就是一個對象應當對其他對象有儘可能少的瞭解

再設計系統時,應該儘可能的減少對象之間的交互

有一個形象的說法"不要和“陌生人”說話、只與你的直接朋友通信"

下麵這些一般被認為是朋友

 

(1) 當前對象本身(this); (2) 以參數形式傳入到當前對象方法中的對象; (3) 當前對象的成員對象; (4) 如果當前對象的成員對象是一個集合,那麼集合中的元素也都是朋友; (5) 當前對象所創建的對象 其實也仍舊是"不要和其他對象有過多的聯繫",只在必要的時候與外界進行聯繫

 

"不要和“陌生人”說話、只與你的直接朋友通信" 就能夠最大程度的降低耦合性

類之間的耦合度越低,就越有利於復用

如果兩個對象之間不是必須要直接通信,那麼這兩個對象就可以不發生任何直接的相互作用

而是可以通過第三者轉發這個調用,通過引入第三者將耦合度降低

 

設計原則總結

 

開閉原則 軟體實體應該對擴展開放,對修改關閉。 開閉原則是設計原則的核心原則,其他的設計原則都是開閉原則表現和補充。實現開閉原則的方法就是抽象。 單一職責原則 一個類應該只承擔一種責任。 里氏替換原則 所有引用基類的地方必須能透明地使用其子類的對象。 依賴倒置原則 面向抽象編程,不要面向具體編程。 介面隔離原則 使用專門的介面,而不是大而全統一的介面,不要強迫客戶端程式依賴不需要的方法。 聚合/組合復用原則 如果可以,應該使用組合而不是繼承來達到代碼復用的目的。 迪米特法則: 軟體實體之間應該做到最少的交互。不要和陌生人說話。  

設計原則要求

設計原則是指導思想,將規則落實到具體的類/介面的設計、功能邏輯的劃分上,可以轉化成以下要求

所有的要求都有一個前提:如果可以,應該優先考慮,儘可能的

  • 面向抽象(抽象類、介面)編程,而不是面向實現編程
  • 介面和類的功能要儘可能的單一,避免大而全的類和介面
  • 優先使用組合,而不是繼承
  • 子類的所有方法必須在父類中聲明,或子類必須實現父類中聲明的所有方法
  • 子類應該儘可能的與父類保持一致,不要重寫父類原有邏輯
  • 如果類之間沒必要直接交互,可以通過“中介”,而不是直接交互,降低耦合性
  • 實現和細節可以通過DI的方式,最大程度減少“硬編碼”
  • 如果沒有什麼明顯弊端,類應該被設計成不變的
  • 降低其他類對自身的訪問許可權,不要暴露內部屬性成員,如果需要提供相應的訪問器(屬性) 
 

設計模式與設計原則

設計原則是軟體開發過程中,前人以“高內聚,低耦合” “提高復用性”“提高可維護性”為根本目標

在實踐中總結出來的經驗,進而演化出來的具體的行為準則

 

就好似要做“好”一件事情,那麼“好”的標準是什麼?

按照經驗總結歸納出來的一些“好”的標準,就是程式設計中的設計原則

 

設計原則是站在不同的維度與角度思考問題的, 他們的根本目的是相同的

本質都是為了設計一個“易維護、可復用、高內聚低耦合”的程式

比如單一職責原則與介面隔離原則,本質都是要職責專一

類提供單一的功能的實現,介面不要有大而全的功能約定

職責專一就能降低耦合,就更有可能被覆用

使用組合而不是繼承可以避免子類對父類的修改這種情況也就符合了里氏替換原則,也就符合了開閉原則

依賴倒置原則要求面向抽象進行編程而不是面向具體細節,而且依賴註入DI的思想也是如日中天Spring的根本

 

“易維護、可復用、高內聚低耦合”是目標

設計原則是為了達到目標的具體規則

而設計模式則是符合設計規則的具體的類/介面的設計解決方案

也就是設計原則的具體化形式

更準確的說,一個設計良好的程式應該遵循的是設計原則,而並非一定是某個設計模式

所有的原則都是指導方針,而不是硬性規則

是在很多場景下一種優秀的解決方案,而並不是一成不變的

在實際的項目中,你既不能完全放棄使用繼承,也可能讓一個類完全不同“陌生人”講話

也不可能子類完全不重寫父類的方法

面向抽象進行編程,你也不可能讓項目中所有的類都有抽象對應,這也是不可能的,也不能是被允許的

設計模式設計原則是經驗之談,當然是非常寶貴的經驗,也是經過實踐檢驗過的

但是最大的忌諱就是生搬硬套,矯枉過正,那將是最失敗的設計模式的應用方式

 

設計模式和麵向對象的設計原則是解決問題的一般思路

而不是像交規一樣,必須遵守,嚴格執行

不遵守設計原則與設計模式也不會編譯失敗

但是希望能夠盡最大可能的遵守, 當然,還需要因地制宜而不能生搬硬套

或許,你從來不遵守原則,也不使用設計模式,你的代碼可能看起來仍舊好好地

但是

你的代碼出問題的概率

卻會比使用了設計模式遵循了設計原則的代碼

要大得多

 

設計模式和設計原則正是為了能夠更加簡單便利的復用代碼,儘可能的減少問題的出現

就好像一條淺淺的小河,可能有無數種趟過去方案

但是,那條走的人最多的,可能它並不是最好的

但是他肯定是比較合適的一條途徑,不會出現碎玻璃,沙坑等陷阱.

到底是站在巨人的肩膀上還是一定要自己摸著石頭過河?

 

簡單說來就是:我們知道軟體的目標“正確、健壯、靈活、可重用、高效....”等等,總之都是往“優秀”“好”的方向

然後發現了好的軟體的一些特性,所以作為了設計原則

但是還是過於抽象,於是針對於不同的場景,按照設計原則,整理出來一套好的解決方法,這就是設計模式。

 

設計模式分類

關於設計模式與設計原則,設計模式是設計原則的具體化形式,是針對於某些特定場景的具體化解決方案 具體到類/介面的設計組織邏輯 既然是原則的具體化形式,那麼必然,按照原則的合理組合運用以及問題的場景,其實可以延伸出來更多的設計模式 在Design Patterns - Elements of Reusable Object-Oriented Software(中文譯名:設計模式 - 可復用面向對象軟體的基礎)中 有23種設計模式,按照特點可以將其分為三大類型:創建型結構型行為型  創建型模式是用來創建對象的模式,抽象了實例的創建過程,封裝了創建邏輯
  • 將系統所使用的具體類的信息封裝起來
  • 隱藏了類的實例是如何被創建和組織的
結構型模式討論的是類和對象的結構,繼承和組合結構 採用繼承機制來組合介面或實現(類結構型模式),或者通過組合一些對象實現新的功能(對象結構型模式) 結構中的各個不同角色組織在一起以提供更強大的、邏輯清晰的功能 結構性模式的類層次結構的組織上有很大的相似性,但是邏輯的側重功能點是不同的   行為型設計模式關註的是對象的行為,用來解決對象之間的聯繫/通信問題 也就是對象之間針對於不同的問題場景,如何進行合理交互   創建型 工廠模式(Factory Pattern) 抽象工廠模式(Abstract Factory Pattern) 單例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern)   結構型 適配器模式(Adapter Pattern) 橋接模式(Bridge Pattern) 組合模式(Composite Pattern) 裝飾器模式(Decorator Pattern) 外觀模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern)  

行為型

責任鏈模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解釋器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 備忘錄模式(Memento Pattern) 觀察者模式(Observer Pattern) 狀態模式(State Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 訪問者模式(Visitor Pattern)

 

設計模式之間並不是孤立的,他們也會相互使用,下圖為《設計模式 - 可復用面向對象軟體的基礎》一書中的描述

各個模式之間的區別和聯繫是一個“悟”的過程,不要試圖對下下圖進行任何記憶

 

 

image_5bdc195e_2288

另外還有範圍準則的概念,指定模式主要是用於類還是用於對象

類模式處理類和子類之間的關係,這些關係通過繼承建立,是靜態的,編譯時刻便已經確定下來了

對象模式處理對象之間的關係,這些關係在運行時是可以變化的,更具有動態性

其實如果較真,很多的模式都有涉及到繼承/實現

所以說設計模式中常說的“類模式”只是指那些集中於處理類間關係的模式

大部分模式都屬於對象模式

比如對於創建型來說分為 類創建型模式和對象創建型模式

概念只是為了更好的描述問題,類模式和對象模式的概念也來自《設計模式 - 可復用面向對象軟體的基礎》

本人認為對於設計模式一般的學習與理解,這個概念無所謂

 

總結

設計模式是設計原則在解決具體問題時實踐中的運用

所以根本是要理解設計原則的含義

隨著技術發展,會出現更多的不同的問題場景,基於設計原則,可能拓展出來更多的設計模式

事實上到目前為止,也不僅僅是23種

所以說設計模式的根本是設計原則,而設計原則又是為了達到實現一個“優秀”軟體的行為準則。

在你還不能靈活的運用設計原則時,設計模式則是你的墊腳石,讓你在具體的問題面前能夠寫出更好地代碼

設計模式是理論層次的研究學習,自然是枯燥的

而且很難能夠一開始就高屋建瓴的自頂而下的深入理解

也很難徹底領悟設計原則本身

所以,從一個一個模式的學習中慢慢品味設計原則的精髓

 

類、介面之間的層級結構是可以變換的,設計模式的根本是設計原則

所以說在學習中要領悟設計模式的根本思想使用場景

在實踐中,不要生搬硬套的應用模式,也無需同設計模式中的類、介面設計層級結構一模一樣

可能你應用了某個模式,但是可能又根據實際業務有一些變動或調整

有人說,你這不是設計模式,那又如何?

只要能夠滿足需求符合設計原則,往“可復用/易維護/高內聚/低耦合”的目標前進,就好~

 

設計模式將“只可意會,不可言傳”轉變為“不只意會,還可以言傳~”

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本篇文章主要介紹了Vue學習筆記之vue-router的基本使用和配置,具有一定的參考價值,有興趣的可以瞭解一下。 ...
  • 在為項目選擇配色方案時,有什麼提供靈感?我打賭你有一些論點和原則。例如,我們都意識到每種顏色都有其個性和心理底蘊,使其有助於喚起特定的情感。例如,藍色激發了信任,被企業廣應用。這就是眾多公司和機構選擇它作為主要原因的原因。紅色點燃了食欲 - 這就是為什麼大多數食品和飲料行業巨頭都會在其標識中使用它。... ...
  • 學習筆記, 來源:http://www.cnblogs.com/zuiyirenjian/p/3535126.html 作者:醉意人間 此外,關於自運行函數可參考 http://benalman.com/news/2010/11/immediately-invoked-function-expres ...
  • 今天有朋友問我關於微信小程式中如何在不占用大量網路帶寬的情況下快速載入圖片,我給他推薦了兩種方式 1.雪碧圖(css script),有過前端經驗的朋友應該都有接觸過。 2.懶載入。 由於時間關係我就先為大家介紹第一種雪碧圖載入,其實雪碧圖載入就是將多張大小尺寸基本相同類型的圖片 拼湊在一起形成一張 ...
  • 最近一些直播、小視頻什麼的都比較火,像陌陌、抖音、火山短視頻… 於是空閑時間自己也利用html5技術也試著倒騰了下直播項目,使用到了h5+css3+iscroll+zepot+swiper+wlsPop架構開發了一個仿陌陌、火山小視頻,項目效果挺不錯噠!同時解決了在直播頁面聊天時候頁面撐起的問題。 ...
  • AngularJS是一款來自Google的前端JS框架,它的核心特性有:MVC、雙向數據綁定、指令和語義化標簽、模塊化工具、依賴註入、HTML模板,以及對常用工具的封裝,例如$http、$cookies、$location等。AngularJS框架的體積非常小,但是設計理念和功能卻非常強大,值得前端 ...
  • JavaScript: 知識點回顧篇(八):js中的瀏覽器對象 ...
  • 本教程基於angular7(2018 11 04) 1. 安裝node.js 下載地址: http://nodejs.cn/download/ 下載對應自己操作系統的版本安裝即可。 2.安裝 angular cli開發套件 安裝命令: 3.創建一個新的項目 ​ 會自動打開瀏覽器並預覽項目,如不能自動 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...