Python學習路程-常用設計模式學習


本節內容

 

  1. 設計模式介紹
  2. 設計模式分類
  3. 設計模式6大原則

1.設計模式介紹

設計模式(Design Patterns)

                                  ——可復用面向對象軟件的基礎

設計模式(Design pattern)是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重復發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。

 

2. 設計模式分類

經典的《設計模式》一書歸納出23種設計模式,這23種模式又可歸為,創建型、結構型和行為型3大類

2.1.創建型模式

前面講過,社會化的分工越來越細,自然在軟件設計方面也是如此,因此對象的創建和對象的使用分開也就成為了必然趨勢。因為對象的創建會消耗掉系統的很多資源,所以單獨對對象的創建進行研究,從而能夠高效地創建對象就是創建型模式要探討的問題。這里有6個具體的創建型模式可供研究,它們分別是:

簡單工廠模式(Simple Factory);

工廠方法模式(Factory Method);

抽象工廠模式(Abstract Factory);

創建者模式(Builder);

原型模式(Prototype);

單例模式(Singleton)。

說明:嚴格來說,簡單工廠模式不是GoF總結出來的23種設計模式之一。

2.2 結構型模式

在解決了對象的創建問題之后,對象的組成以及對象之間的依賴關系就成了開發人員關注的焦點,因為如何設計對象的結構、繼承和依賴關系會影響到后續程序的維護性、代碼的健壯性、耦合性等。對象結構的設計很容易體現出設計人員水平的高低,這里有7個具體的結構型模式可供研究,它們分別是:

外觀模式(Facade);

適配器模式(Adapter);

代理模式(Proxy);

裝飾模式(Decorator);

橋模式(Bridge);

組合模式(Composite);

享元模式(Flyweight)

 

2.3 行為型模式

在對象的結構和對象的創建問題都解決了之后,就剩下對象的行為問題了,如果對象的行為設計的好,那么對象的行為就會更清晰,它們之間的協作效率就會提高,這里有11個具體的行為型模式可供研究,它們分別是:

模板方法模式(Template Method);

觀察者模式(Observer);

狀態模式(State);

策略模式(Strategy);

職責鏈模式(Chain of Responsibility);

命令模式(Command);

訪問者模式(Visitor);

調停者模式(Mediator);

備忘錄模式(Memento);

迭代器模式(Iterator);

解釋器模式(Interpreter)。

 

3. 設計模式的六大原則

1、開閉原則(Open Close Principle)

開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:為了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,后面的具體設計中我們會提到這點。

2、里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規范。—— From Baidu 百科

3、依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,具體內容:真對接口編程,依賴於抽象而不依賴於具體。

4、接口隔離原則(Interface Segregation Principle)

這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,為了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。

5、迪米特法則(最少知道原則)(Demeter Principle)

為什么叫最少知道原則,就是說:一個實體應當盡量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。

6、合成復用原則(Composite Reuse Principle)

原則是盡量使用合成/聚合的方式,而不是使用繼承。

 

工廠模式

工廠模式(Factory Pattern)是 Java 中最常用的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。

在工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,並且是通過使用一個共同的接口來指向新創建的對象。

 

意圖:

定義一個用於創建對象的接口,讓子類決定實例化哪一個類。Factory Method 使個類的實例化延遲到其子類。 

適用性:

當一個類不知道它所必須創建的對象的類的時候。

當一個類希望由它的子類來指定它所創建的對象的時候。

當類將創建對象的職責委托給多個幫助子類中的某一個,並且你希望將哪一個幫助子類是代理者這一信息局部化的時候。

簡單工廠模式

優點:客戶端不需要修改代碼。
缺點: 當需要增加新的運算類的時候,不僅需新加運算類,還要修改工廠類,違反了開閉原則。  

工廠方法模式

這個和簡單工廠有區別,簡單工廠模式只有一個工廠,工廠方法模式對每一個產品都有相應的工廠

  好處:增加一個運算類(例如N次方類),只需要增加運算類和相對應的工廠,兩個類,不需要修改工廠類。

  缺點:增加運算類,會修改客戶端代碼,工廠方法只是把簡單工廠的內部邏輯判斷移到了客戶端進行。

  

抽象工廠模式

每一個模式都是針對一定問題的解決方案。抽象工廠模式與工廠方法模式的最大區別就在於,工廠方法模式針對的是一個產品等級結構;而抽象工廠模式則需要面對多個產品等級結構。

  在學習抽象工廠具體實例之前,應該明白兩個重要的概念:產品族和產品等級。

所謂產品族,是指位於不同產品等級結構中,功能相關聯的產品組成的家族。比如AMD的主板、芯片組、CPU組成一個家族,Intel的主板、芯片組、CPU組成一個家族。而這兩個家族都來自於三個產品等級:主板、芯片組、CPU。一個等級結構是由相同的結構的產品組成,示意圖如下:

顯然,每一個產品族中含有產品的數目,與產品等級結構的數目是相等的。產品的等級結構與產品族將產品按照不同方向划分,形成一個二維的坐標系。橫軸表示產品的等級結構,縱軸表示產品族,上圖共有兩個產品族,分布於三個不同的產品等級結構中。只要指明一個產品所處的產品族以及它所屬的等級結構,就可以唯一的確定這個產品。

  上面所給出的三個不同的等級結構具有平行的結構。因此,如果采用工廠方法模式,就勢必要使用三個獨立的工廠等級結構來對付這三個產品等級結構。由於這三個產品等級結構的相似性,會導致三個平行的工廠等級結構。隨着產品等級結構的數目的增加,工廠方法模式所給出的工廠等級結構的數目也會隨之增加。如下圖:

那么,是否可以使用同一個工廠等級結構來對付這些相同或者極為相似的產品等級結構呢?當然可以的,而且這就是抽象工廠模式的好處。同一個工廠等級結構負責三個不同產品等級結構中的產品對象的創建。

 

 可以看出,一個工廠等級結構可以創建出分屬於不同產品等級結構的一個產品族中的所有對象。顯然,這時候抽象工廠模式比簡單工廠模式、工廠方法模式更有效率。對應於每一個產品族都有一個具體工廠。而每一個具體工廠負責創建屬於同一個產品族,但是分屬於不同等級結構的產品。

 

抽象工廠模式結構

  抽象工廠模式是對象的創建模式,它是工廠方法模式的進一步推廣。

  假設一個子系統需要一些產品對象,而這些產品又屬於一個以上的產品等級結構。那么為了將消費這些產品對象的責任和創建這些產品對象的責任分割開來,可以引進抽象工廠模式。這樣的話,消費產品的一方不需要直接參與產品的創建工作,而只需要向一個公用的工廠接口請求所需要的產品。

  通過使用抽象工廠模式,可以處理具有相同(或者相似)等級結構中的多個產品族中的產品對象的創建問題。如下圖所示:

 

  由於這兩個產品族的等級結構相同,因此使用同一個工廠族也可以處理這兩個產品族的創建問題,這就是抽象工廠模式。

  根據產品角色的結構圖,就不難給出工廠角色的結構設計圖。

 

可以看出,每一個工廠角色都有兩個工廠方法,分別負責創建分屬不同產品等級結構的產品對象。

 

抽象工廠的功能是為一系列相關對象或相互依賴的對象創建一個接口。一定要注意,這個接口內的方法不是任意堆砌的,而是一系列相關或相互依賴的方法。比如上面例子中的主板和CPU,都是為了組裝一台電腦的相關對象。不同的裝機方案,代表一種具體的電腦系列。

由於抽象工廠定義的一系列對象通常是相關或相互依賴的,這些產品對象就構成了一個產品族,也就是抽象工廠定義了一個產品族。

  這就帶來非常大的靈活性,切換產品族的時候,只要提供不同的抽象工廠實現就可以了,也就是說現在是以一個產品族作為一個整體被切換。

 

在什么情況下應當使用抽象工廠模式

  1.一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有形態的工廠模式都是重要的。

  2.這個系統的產品有多於一個的產品族,而系統只消費其中某一族的產品。

  3.同屬於同一個產品族的產品是在一起使用的,這一約束必須在系統的設計中體現出來。(比如:Intel主板必須使用Intel CPU、Intel芯片組)

  4.系統提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於實現。

抽象工廠模式的起源

  抽象工廠模式的起源或者最早的應用,是用於創建分屬於不同操作系統的視窗構建。比如:命令按鍵(Button)與文字框(Text)都是視窗構建,在UNIX操作系統的視窗環境和Windows操作系統的視窗環境中,這兩個構建有不同的本地實現,它們的細節有所不同。

  在每一個操作系統中,都有一個視窗構建組成的構建家族。在這里就是Button和Text組成的產品族。而每一個視窗構件都構成自己的等級結構,由一個抽象角色給出抽象的功能描述,而由具體子類給出不同操作系統下的具體實現。

 

抽象工廠模式的優點

  • 分離接口和實現

  客戶端使用抽象工廠來創建需要的對象,而客戶端根本就不知道具體的實現是誰,客戶端只是面向產品的接口編程而已。也就是說,客戶端從具體的產品實現中解耦。

  • 使切換產品族變得容易

  因為一個具體的工廠實現代表的是一個產品族,比如上面例子的從Intel系列到AMD系列只需要切換一下具體工廠。

抽象工廠模式的缺點

  • 不太容易擴展新的產品

  如果需要給整個產品族添加一個新的產品,那么就需要修改抽象工廠,這樣就會導致修改所有的工廠實現類。

 

  

建造者模式

意圖:

將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。

適用性:

當創建復雜對象的算法應該獨立於該對象的組成部分以及它們的裝配方式時。

當構造過程必須允許被構造的對象有不同的表示時。

 

 

  

 單例模式

意圖: 

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

適用性:

當類只能有一個實例而且客戶可以從一個眾所周知的訪問點訪問它時。

當這個唯一實例應該是通過子類化可擴展的,並且客戶應該無需更改代碼就能使用一個擴展的實例時。

 

  

適配器模式 

意圖

將一個類的接口轉換成客戶希望的另外一個接口。Adapter 模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。 
適用性:

你想使用一個已經存在的類,而它的接口不符合你的需求。

你想創建一個可以復用的類,該類可以與其他不相關的類或不可預見的類(即那些接口可能不一定兼容的類)協同工作

 

  

橋接模式

參考:http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html


生活中的一個例子:
就拿汽車在路上行駛的來說。即有小汽車又有公共汽車,它們都不但能在市區中的公路上行駛,也能在高速公路上行駛。這你會發現,對於交通工具(汽車)有不同的類型,然而它們所行駛的環境(路)也在變化,在軟件系統中就要適應兩個方面的變化?怎樣實現才能應對這種變化呢?
概述:
在軟件系統中,某些類型由於自身的邏輯,它具有兩個或多個維度的變化,那么如何應對這種“多維度的變化”?如何利用面向對象的技術來使得該類型能夠輕松的沿着多個方向進行變化,而又不引入額外的復雜度?這就要使用Bridge模式。
意圖:
   將抽象部分與實現部分分離,使它們都可以獨立的變化。
——《設計模式》GOF

效果及實現要點:
1.Bridge模式使用“對象間的組合關系”解耦了抽象和實現之間固有的綁定關系,使得抽象和實現可以沿着各自的維度來變化。
2.所謂抽象和實現沿着各自維度的變化,即“子類化”它們,得到各個子類之后,便可以任意它們,從而獲得不同路上的不同汽車。
3.Bridge模式有時候類似於多繼承方案,但是多繼承方案往往違背了類的單一職責原則(即一個類只有一個變化的原因),復用性比較差。Bridge模式是比多繼承方案更好的解決方法。
4.Bridge模式的應用一般在“兩個非常強的變化維度”,有時候即使有兩個變化的維度,但是某個方向的變化維度並不劇烈——換言之兩個變化不會導致縱橫交錯的結果,並不一定要使用Bridge模式。

適用性:
在以下的情況下應當使用橋梁模式:
1.如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的聯系。
2.設計要求實現化角色的任何改變不應當影響客戶端,或者說實現化角色的改變對客戶端是完全透明的。
3.一個構件有多於一個的抽象化角色和實現化角色,系統需要它們之間進行動態耦合。
4.雖然在系統中使用繼承是沒有問題的,但是由於抽象化角色和具體化角色需要獨立變化,設計要求需要獨立管理這兩者。
總結:
Bridge模式是一個非常有用的模式,也非常復雜,它很好的符合了開放-封閉原則和優先使用對象,而不是繼承這兩個面向對象原則

 

應用設計模式:
       橋接模式(Bridge)來做(多維度變化);
       結合上面的例子,增加一個維度"人",不同的人開着不同的汽車在不同的路上行駛(三個維度);
       結合上面增加一個類"人",並重新調用.
代碼實現:

  

組合模式

意圖:

將對象組合成樹形結構以表示“部分-整體”的層次結構。C o m p o s i t e 使得用戶對單個對象和組合對象的使用具有一致性。 
適用性:

你想表示對象的部分-整體層次結構。

你希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象。

 

  

外觀模式

意圖:

為子系統中的一組接口提供一個一致的界面,Facade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。

適用性:

當你要為一個復雜子系統提供一個簡單接口時。子系統往往因為不斷演化而變得越來越復雜。大多數模式使用時都會產生更多更小的類。這使得子系統更具可重用性,也更容易對子系統進行定制,但這也給那些不需要定制子系統的用戶帶來一些使用上的困難。Facade 可以提供一個簡單的缺省視圖,這一視圖對大多數用戶來說已經足夠,而那些需要更多的可定制性的用戶可以越過facade層。

客戶程序與抽象類的實現部分之間存在着很大的依賴性。引入facade 將這個子系統與客戶以及其他的子系統分離,可以提高子系統的獨立性和可移植性。

當你需要構建一個層次結構的子系統時,使用facade模式定義子系統中每層的入口點。如果子系統之間是相互依賴的,你可以讓它們僅通過facade進行通訊,從而簡化了它們之間的依賴關系。  

 

  

享元模式

意圖:

運用共享技術有效地支持大量細粒度的對象。

適用性:

一個應用程序使用了大量的對象。

完全由於使用大量的對象,造成很大的存儲開銷。

對象的大多數狀態都可變為外部狀態。

如果刪除對象的外部狀態,那么可以用相對較少的共享對象取代很多組對象。

應用程序不依賴於對象標識。由於Flyweight 對象可以被共享,對於概念上明顯有別的對象,標識測試將返回真值。

  

代理模式

意圖:為其他對象提供一種代理以控制對這個對象的訪問。

主要解決:在直接訪問對象時帶來的問題,比如說:要訪問的對象在遠程的機器上。在面向對象系統中,有些對象由於某些原因(比如對象創建開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此對象時加上一個對此對象的訪問層。

何時使用:想在訪問一個類時做一些控制。

如何解決:增加中間層。

關鍵代碼:實現與被代理類組合。

應用實例: 1、Windows 里面的快捷方式。 2、豬八戒去找高翠蘭結果是孫悟空變的,可以這樣理解:把高翠蘭的外貌抽象出來,高翠蘭本人和孫悟空都實現了這個接口,豬八戒訪問高翠蘭的時候看不出來這個是孫悟空,所以說孫悟空是高翠蘭代理類。 3、買火車票不一定在火車站買,也可以去代售點。 4、一張支票或銀行存單是賬戶中資金的代理。支票在市場交易中用來代替現金,並提供對簽發人賬號上資金的控制。 5、spring aop。

優點: 1、職責清晰。 2、高擴展性。 3、智能化。

缺點: 1、由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。 2、實現代理模式需要額外的工作,有些代理模式的實現非常復雜。

使用場景:按職責來划分,通常有以下使用場景: 1、遠程代理。 2、虛擬代理。 3、Copy-on-Write 代理。 4、保護(Protect or Access)代理。 5、Cache代理。 6、防火牆(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

 

 

 

模板方法模式

在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但調用將以抽象類中定義的方式進行。這種類型的設計模式屬於行為型模式。

意圖:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。

主要解決:一些方法通用,卻在每一個子類都重新寫了這一方法。

何時使用:有一些通用的方法。

如何解決:將這些通用算法抽象出來。

關鍵代碼:在抽象類實現,其他步驟在子類實現。

應用實例: 1、在造房子的時候,地基、走線、水管都一樣,只有在建築的后期才有加壁櫥加柵欄等差異。 2、西游記里面菩薩定好的 81 難,這就是一個頂層的邏輯骨架。 3、Spirng 中對 Hibernate 的支持,將一些已經定好的方法封裝起來,比如開啟事務、獲取 Session、關閉 Session 等,程序員不重復寫那些已經規范好的代碼,直接丟一個實體就可以保存。

優點: 1、封裝不變部分,擴展可變部分。 2、提取公共代碼,便於維護。 3、行為由父類控制,子類實現。

缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。

使用場景: 1、有多個子類共有的方法,且邏輯相同。 2、重要的、復雜的方法,可以考慮作為模板方法。

  

責任鏈模式

意圖:

使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它為止。

適用性:

有多個的對象可以處理一個請求,哪個對象處理該請求運行時刻自動確定。

你想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。

可處理一個請求的對象集合應被動態指定。

 

 

 

觀察者模式

意圖:

定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時, 所有依賴於它的對象都得到通知並被自動更新。

適用性:

當一個抽象模型有兩個方面, 其中一個方面依賴於另一方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。

當對一個對象的改變需要同時改變其它對象, 而不知道具體有多少對象有待改變。

當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之, 你不希望這些對象是緊密耦合的。

 

 

 

  

策略模式

意圖:定義一系列的算法,把它們一個個封裝起來, 並且使它們可相互替換。

主要解決:在有多種算法相似的情況下,使用 if...else 所帶來的復雜和難以維護。

何時使用:一個系統有許多許多類,而區分它們的只是他們直接的行為。

如何解決:將這些算法封裝成一個一個的類,任意地替換。

關鍵代碼:實現同一個接口。

應用實例: 1、諸葛亮的錦囊妙計,每一個錦囊就是一個策略。 2、旅行的出游方式,選擇騎自行車、坐汽車,每一種旅行方式都是一個策略。 3、JAVA AWT 中的 LayoutManager。

優點: 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴展性良好。

缺點: 1、策略類會增多。 2、所有策略類都需要對外暴露。

使用場景: 1、如果在一個系統里面有許多類,它們之間的區別僅在於它們的行為,那么使用策略模式可以動態地讓一個對象在許多行為中選擇一種行為。 2、一個系統需要動態地在幾種算法中選擇一種。 3、如果一個對象有很多的行為,如果不用恰當的模式,這些行為就只好使用多重的條件選擇語句來實現。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#_*_coding:utf-8_*_
__author__  =  'Alex Li'
 
 
 
class  TravelStrategy( object ):
     '''
     出行策略
     '''
 
     def  travelAlgorithm( self ):
         pass
 
class  AirplaneStrategy(TravelStrategy):
     def  travelAlgorithm( self ):
         print ( "坐飛機出行...." )
 
class  TrainStrategy(TravelStrategy):
     def  travelAlgorithm( self ):
         print ( "坐高鐵出行...." )
 
 
class  CarStrategy(TravelStrategy):
     def  travelAlgorithm( self ):
         print ( "自駕出行...." )
 
class  BicycleStrategy(TravelStrategy):
     def  travelAlgorithm( self ):
         print ( "騎車出行...." )
 
 
class  TravelInterface( object ):
     def  __init__( self ,travel_strategy):
         self .travel_strategy  =  travel_strategy
 
     def  set_strategy( self ,travel_strategy):
         self .travel_strategy  =  travel_strategy
     def  travel( self ):
         return  self .travel_strategy.travelAlgorithm()
 
 
 
#坐飛機
travel  =  TravelInterface(AirplaneStrategy())
 
travel.travel()
 
#改開車
travel.set_strategy(TrainStrategy())
travel.travel()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM