面向對象程序設計進階——設計模式 design patterns

前言

設計模式(design pattern)是一套被反覆使用、多數人知曉、經過分類編目的優秀代碼設計經驗的總結。關鍵詞:重用代碼、工程化、面向對象。設計模式起源於建築設計學,最先由 Gang of Four   提升到了理論高度。

可複用面向對象體系分爲兩大系統:工具箱和框架。Java中的API屬於工具箱(toolkit),Java EE (Enterprise Edition)屬於框架(Framework)。設計模式是大神們在構造 Java EE 的時候的重要理論依據,學習設計模式有助於深入瞭解 Java EE。


我最近在實驗樓 學習完了一門課程《Java進階之設計模式》。截止至2015年12月16日,我已經在實驗樓網站有效學習了960分鐘,完整學習了5門課程。功夫在課外,我認爲實驗樓是一個能夠開闊視野,快速入門新領域的地方。其主要特點是:課程設置廣泛,內容深入淺出,提供linux環境。

GOF最早提出的設計模式總共有23個,分爲三類型:創建型模式(5個),構造型模式(7個),行爲型模式(11個)。後來,人們總結出了更多的設計模式,參考wikipedia

  • 創建型:單例模式、工廠方法模式、抽象工廠模式、建造者模式、原型模式
  • 構造性:適配器模式、裝飾模式、代理模式、組合模式、橋樑模式、外觀模式、享元模式
  • 行爲型:模板方法模式、命令模式、責任鏈模式、迭代器模式、中介者模式、觀察者模式、訪問者模式、狀態模式、解釋器模式

實驗樓裏面講解了其中的6個,這6個大體上是23箇中使用頻率最高的6個:工廠方法模式、抽象工廠模式、單例模式、適配器模式、裝飾者模式、觀察者模式。我下面將首先簡單介紹設計原則,然後小結一下上述常用6種模式。我寫本篇博客使用的參考資料包括:實驗樓的課程、《Head First Design Patterns》、《設計模式(Java版)》、wikipedia、百度百科。


設計原則

  • 開閉原則(OCP):open for extension, but close for modification。在設計一個模塊的時候,應當使這個模塊可以在不被修改的前提下被擴展。這個是最最基礎的原則。

  • 單一職責原則(SRP):never be more than one reason for a class to change。專注做一件事情,僅有一個引起變化的原因,“職責”可以理解成“變化的原因”。唯有專注,才能夠保證對象的高內聚;唯有單一,才能保證對象的細粒度。

  • 里氏替換原則(LSP):Liskov提出:” Let Φ(x) be a property provable about objects x of type T . Then  Φ(y) should be true for objects y of type S , where S is a subtype of T .” 這裏的type,我理解成class。子類的對象可以無條件地替換父類的對象,並且不引起程序的改變。或者說:只要父類能出現的地方,子類就可以出現。

  • 依賴倒置原則(DIP):High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. 即高層模塊不依賴低層模塊;高、低層模塊都依賴其抽象;抽象不依賴細節,反過來細節依賴抽象。在JAVA中,所謂的抽象,就是接口或者抽象類;所謂細節,就是就實現類(能夠實例化生成對象的類)。爲什麼說“倒置”?因爲在傳統的面向過程的設計中,高層次的模塊依賴於低層次的模塊,抽象層依賴於具體層(這樣不好),倒置過來後,就成爲了面向對象設計的一個原則了(這樣子好)。

  • 接口隔離原則(ISP):The dependency of one class to another should depend on the smallest possible interface. 類間的依賴關係應該建立在最小的接口上面。客戶端不應該依賴他不需要的接口,只提供調用者需要的方法,屏蔽不需要的方法。一個接口代表一個角色,使用多個專門的接口比使用單一的總接口更好。

  • 迪米特法則(LoD)又稱最少知識原則(LKP): Each unit should only talk to its friends; don’t talk to strangers. Only talk to your immediate friends. Each unit should have only limited knowledge about other units. 曾經在美國有一個項目叫做Demeter。LoD is a specific case of loose coupling.此原則的核心觀念就是類之間的弱耦合,在這種情況下,類的複用率纔可以提升。


工廠方法模式

Head First (簡稱HF ) 說:There is more to making objects than just using the new operator. Instantiation is an activity that shouldn’t always be done in public and can often lead to coupling problems. 這就是工廠方法的來源。

首先簡單提一下:簡單工廠。HF : The Simple Factory isn’t actually a Design Pattern; it’s more of a programming idiom. 代碼實現:把 new操作的過程封裝到一個class SimpleFactory 的一個方法中。這個方法有一個參數,這個參數決定如何new。

簡單工廠進行抽象化,得到工廠方法模式。工廠方法模式定義:Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method let a class defer instantiation to subclasses. 簡而言之,一個工廠父類作爲“總管”,管理旗下的具體工廠子類,new 操作在具體工廠子類裏面。定義中”defer”的意思是:父類不立刻進行new操作,而是交給子類進行new操作。

代碼工廠方法模式例子Java源代碼 (推薦在新標籤頁打開)。我改編自HF 的pizza例子(我本學期早上流行喫包子)

小結:(看完代碼之後再看小結)
一個工廠子類,看上去很像一個簡單工廠。確實如此,不過這一些工廠子類都繼承自同一個工廠父類,需要實現同一個抽象方法createBaoZi,然後利用多態性。簡單工廠像是一個“一錘子買賣”,而工廠方法模式則搭建了一個框架,讓工廠子類決定生產那一個具體產品。工廠方法模式更加抽象,更具有通用性,耦合度更低。

在Main類裏面,new 一個工廠子類,把其引用賦給一個工廠父類的引用。從代碼層面可以看出,當需要擴充新的工廠的時候,增加一個繼承工廠父類的子類就行了。Main類裏面的那個工廠父類的引用無需改變。更進一步說,Main類裏面的所有的用到工廠父類的引用的地方都無需改變,這就是體現了開閉原則的中的“close for modification”


抽象工廠模式

抽象工廠模式定義:Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

首先需要知道一個概念“a family of products”,翻譯成“產品族”。Head First : Factory Method provides an abstract interface for creating one product. Abstract Factory provides an abstract interface for creating a family of products.

產品族:假設有三類抽象產品:手機、筆記本、平板。手機分爲兩個具體產品:iphone 6s和華爲Mate8;電腦分爲兩個具體產品:Macbook Pro和Surface Book;平板分爲iPad Air和Surface 3。那麼從手機裏面二選一,從筆記本里面二選一,從平板裏面二選一,總共產生了8個不同的產品族。比如”iphone 6s + Macbook Pro + iPad Air”就是8個產品族中的1個(土豪!)。

代碼抽象工廠模式例子Java源代碼 (推薦在新標籤頁打開)

小結:抽象工廠模式是對工廠方法的進一步抽象。是把一組具有同一主題的單獨的工廠封裝起來。抽象工廠可以看成是”工廠的工廠“,抽象工廠中的一個抽象方法,很像一個工廠方法模式。一個工廠有多抽象工廠,就像是有多個工廠方法模式。


單例模式

有時候存在這種需求:強制要求某個類只能實例化一個對象, one and only one. 單例模式解決了這種需求,其定義是:Singleton Pattern ensures a class has only one instance, and provide a global point of access to it.

應用場景:

  • 要求生成唯一序列號的環境
  • 需要共享訪問點或共享數據,例如CSDN每一篇博客都有一個計數器,用來統計閱讀人數,使用單例模式保持計數機的值
  • 需要創建的對象消耗大量資源,比如IO和數據庫資源
  • 需要定義大量的靜態常量或者靜態方法(工具類)的環境,比如Java基礎類庫中的java.lang.Runtime類

仔細想想,如果使用全局變量,則可以實現定義中的後半句,但是無法實現定義中的前半句,而且使用全局變量會污染命名空間。

代碼:單例模式Java源代碼,可以在新標籤頁打開

小結:單例模式,顧名思義,讓一個類有且只有一個實例對象。這樣做可以達到節約或者控制系統資源的目的。在代碼層面,其最主要的特徵是其構造函數是私有的。次要特點是:數據成員Singleton的實例引用是靜態的,而且有一個靜態的getInstance()方法,用來負責 new Singleton() 和返回 Singleton的實例引用。


觀察者模式

這是一個常用的行爲型模式,又稱爲“發佈者——訂閱者”模式。發佈者比如報社,訂閱者比如老百姓。清晨爺爺奶奶們出去晨練,看見一個賣報紙的小男孩在街上大聲吆喝:“今天的報紙來了!”,然後爺爺奶奶們得到通知,都去買了報紙。報社是主題(Subject),爺爺奶奶們是觀察者(Observer),賣報紙的小男孩,負責告訴Observer:“Subject更新了!”

定義:The Observation Pattern defines a one-to-many dependency between objects so that when one objects changes state, all of its dependents are notified and updated automatically.

代碼觀察者模式Java代碼,可以在新標籤頁打開

小結:定義中的one就是Subject, many就是Observers;one就是發佈者,many就是訂閱者;one就是事件源,many就是監聽者。多個觀察者(Observer)“圍觀”一個被觀察者(Subject),可以說被觀察者是萬衆矚目的焦點。觀察者和被觀察者之間是抽象耦合(類圖在上面的鏈接裏),它們屬於不同的抽象化層次,非常容易擴展。使用場景是:關聯行爲;事件多級觸發;消息隊列等。


適配器模式

這是一個結構型模式。周邊適配器到處都是,比如手機充電器就是一個把插座上面的兩孔國標插座,轉換成USB接口。去了美國,怎麼給我的榮耀手機充電?那就那一個適配器,把美國的兩孔/三孔美標插座轉換成兩孔國標插座。

定義:The Adapter Pattern converts the interface of a class into another interface the client expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

在上面的情景中,榮耀手機就是定義中的“client”;兩孔國標接口就是”interface the client expect”;美國的兩孔/三孔美標插座就是定義中 “converts the interface of a class”中的”interface”;Adapter 負責動詞”convert”。

代碼適配器模式 Java源代碼,可以在新標籤頁中打開

小結:適配器Adapter的核心,是實現Target接口, 組合Adaptee接口。通過實現和組合,這兩種類與類之間的關係,把兩個本不兼容的類(Target 和 Adaptee)聯繫在一起。增強了類的透明性,松耦合,提高了類的複用,增強了代碼的靈活性。


裝飾者模式

這是一個結構型模式。

很多情況下,需要擴展功能,增加職責。如果說生成子類是靜態的添加額外的職責的話,那麼裝飾者模式則提供了一種動態的方法,較爲靈活。

定義:attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

代碼裝飾者模式 Java源代碼,可以在新標籤頁中打開

小結:關鍵是Decorator同時繼承和組合Component,Component是被裝飾者裝飾的對象。裝飾者和被裝飾者可以獨立發展,耦合度很低,很好的符合了開閉原則,是繼承關係的一種替代方案。或者說,是一種對繼承關係的有力的補充,可以解決類膨脹的問題。Java.IO就是參照裝飾者模式設計的。


一點感悟

我現在是計算機科學與技術(CS)的大三上學期。設計模式的學習,我都是利用課外時間。我學習設計模式的初衷,除了見見世面之外,就是學習Java(我大一大二都是寫C++,大三上學期才學Java),可謂一箭雙鵰。我下載了《Head First Design Patterns》在GitHub上的樣例代碼, which is written in Java。我一邊看書,一邊看代碼,抄代碼,一邊改編代碼。理論與敲代碼結合,快速提升、強化基本能力。

  • 敲了不少Java代碼,對Java的”感覺”加深了,代碼是需要積累的
  • 我在eclipse裝一個插件,用來繪製UML類圖。我對eclipse的”感覺”加深了,體會到了插件的強大威力
  • 繼承,多態。以前寫C++代碼,只用封裝,很少用繼承和多態。畢竟平時主要在寫”小算法”,沒有涉及到大的宏觀層面的設計。在設計模式中,大量運用繼承(還有實現),多態
  • decouple。這個詞在HF 中高頻率出現。降低耦合,面向接口,可以增強整個程序的擴展性,便於維護,適合多團隊多人合作開發
  • 通過幾個設計模式,慢慢體會了設計模式中的6條原則,這些原則不是”教條主義“,不能刻板遵守。而是要慢慢把這些思想融入到程序設計中
  • 關於算法和設計模式。網上看到一些評論,有一個評論很生動:算法像是”單兵作戰和武器裝備“,設計模式像是”仗列的陣型“。算法用來解決具體問題,設計模式用來合理的把算法隔離到各個正確的地方去。真正體會設計模式,還是需要很多年的實踐積累

設計模式的博客就寫到這裏。最近在較短時間內,集中學習了6個常用設計模式,收穫頗豐。掌握了基本思想和方法,在以後的學習和實踐中,遇到新的需要使用的模式,我就能快速學會和運用。

一個設計模式學習網站

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章