1. 程式人生 > >面向物件程式設計進階——設計模式 design patterns

面向物件程式設計進階——設計模式 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操作。

小結:(看完程式碼之後再看小結)
一個工廠子類,看上去很像一個簡單工廠。確實如此,不過這一些工廠子類都繼承自同一個工廠父類,需要實現同一個抽象方法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個(土豪!)。

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

單例模式

有時候存在這種需求:強制要求某個類只能例項化一個物件, 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類

仔細想想,如果使用全域性變數,則可以實現定義中的後半句,但是無法實現定義中的前半句,而且使用全域性變數會汙染名稱空間。

小結:單例模式,顧名思義,讓一個類有且只有一個例項物件。這樣做可以達到節約或者控制系統資源的目的。在程式碼層面,其最主要的特徵是其建構函式是私有的。次要特點是:資料成員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.

小結:定義中的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”。

小結:介面卡Adapter的核心,是實現Target介面, 組合Adaptee介面。通過實現和組合,這兩種類與類之間的關係,把兩個本不相容的類(Target 和 Adaptee)聯絡在一起。增強了類的透明性,鬆耦合,提高了類的複用,增強了程式碼的靈活性。

裝飾者模式

這是一個結構型模式。

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

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

小結:關鍵是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個常用設計模式,收穫頗豐。掌握了基本思想和方法,在以後的學習和實踐中,遇到新的需要使用的模式,我就能快速學會和運用。