1. 程式人生 > >設計模式詳解:彙總

設計模式詳解:彙總

二十三個設計模式

模式的四個基本要素:

(1)模式名稱(Pattern Name)

(2)問題 Problem

(3)解決方案Solution

(4)效果(Consequences)

1.工廠模式

使用場景:在編碼的時候不能預見需要建立哪種類的例項;

系統不應該依賴於產品類例項如何被建立、組合和表達的細節;

工廠模式主要是為了建立物件提供了介面,一般來說,分為3類:

1.1 簡單工廠模式(Simple Factory)

客戶端通過簡單工廠建立了一個實現了介面的物件,然後通過介面程式設計,從客戶端來看,它根本就不知道具體的實現是什麼,也不知道是如何實現的,它只知道通過工廠獲得了一個介面物件,然後就能夠通過這個介面來獲取到想要的功能。事實上,簡單工廠能夠幫助我們真正的開始面向介面程式設計,像以前的做法,其實只是用到了介面的多型的那部分的功能,最重要的“封裝隔離性”並沒有體現出來;

對於客戶端來講,只是知道了介面API和簡單工廠Factory,通過Factory就可以獲得API了,這樣實現了讓Client在不知道具體實現類的情況下可以獲取Api介面。

簡單工廠方法的內部主要實現的功能就是:選擇合適的介面實現類來建立例項物件。

既然要實現選擇,選擇條件或者是選擇的引數一般如下:

(1)來源於客戶端:由Client來傳入引數;

(2)來源於配置檔案:從配置檔案獲取用於判斷的值;

(3)來源於程式執行期的某個值:比如從快取中獲取某個執行期的值;

問題討論:如果每次增加一個實現類,都來修改工廠類的實現,這肯定不是一個好的實現方式。

解決方法:通常使用配置檔案來解決這個問題,當有了新的實現類的時候,只要在配置檔案中配置新的實現類就可以了,在簡單工廠的方法裡面可以使用反射,當然也可以使用IOC/DI(控制反轉/依賴注入)來實現;

讀取配置檔案方式:

例如配置檔案中:ImplClass=com.lei.Impl

則:

Properties p = new Properties();

InputStream in = 類名.class.getResourceAsStream(檔名.properties);

p.load(in);

//使用反射機制來建立具體的實現類

Class.forName(p.getProperty(“ImplClass”)).newInstance();

簡單工廠的優點:

(1)幫助封裝:然元件外部能夠真正的面向介面程式設計;

(2)解耦:實現了客戶端和具體實現類的解耦;

缺點:

(1)可能增加客戶端的複雜度,

(2)不方便擴充套件子工廠;

簡單工廠的本質:選擇實現;

使用場景:

(1)如果想要完全的封裝隔離具體的實現,讓外部只能通過介面來操作封裝體,那麼就可以選擇簡單工廠,讓客戶端通過工廠來獲取到相應的介面,而無需關心具體的實現;

(2)如果想要把對外建立物件的職責集中管理和控制,可以選擇簡單工廠 ,一個簡單工廠可以建立很多的、不相關的物件;

1.2 工廠方法模式(Factory Method)

1.3 抽象工廠模式(Abstract Factory)

抽象工廠模式和工廠方法模式的區別在於需要建立物件的複雜度上。而且抽象工廠模式是三個裡面最為抽象、最具一般性的;

抽象工廠模式的目的是給客戶端提供一個介面,可以建立多個產品族中的產品物件。而且使

抽象工廠模式中的角色和工廠方法模式的完全相同,由如下4部分組成:

(1)抽象工廠角色:

(2)具體工廠角色:

(3)抽象產品角色:

(4)具體產品角色:

抽象工廠模式可以向客戶端提供一個介面,使得客戶端在不必指定產品具體型別的情況下,建立多個產品族的產品物件。

每個模式都是針對一定問題的解決方案。抽象工廠模式面對的問題是多產品等級結構的系統設計;

重要概念:

(1)產品族:是指位於不同產品等級結構中功能相關的產品組成的家族。比如:AMD的CPU和ADM晶片主機板,組成一個家族。Intel的CPU和Intel晶片的主機板,由組成一個家族;

(2)產品等級:上述兩個家族都來自於兩個產品等級:CPU和主機板。一個等級結構是由相同結構的產品組成的。

抽象工廠模式的每個工廠創造出來的都是一族產品,而不是一個或者一組。組是可以隨意組合的;

抽象工廠模式和工廠方法模式的最大的區別在於:工廠方法模式針對的是一個產品等級結構而抽象工廠模式則需要面對多個產品等級結構。

一個工廠等級結構可以創建出分屬於不同產品等級結構的一個產品族中所有物件。每一個具體工廠負責建立屬於同一個產品族,但是分屬於不同等級結構的產品;

【抽象工廠模式的結構】

抽象工廠模式是物件的建立模式,它是工廠方法模式的進一步推廣;

抽象工廠的功能是為了一系列相關物件或者相互依賴的物件建立一個介面。

注意:該介面內的方法不是隨意的堆砌的,而是一些列相關或者相互依賴的方法;

【抽象工廠的使用場景】

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

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

(3)同屬於一個產品族的產品是在一起使用的;

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

【抽象工廠模式的優點和缺點】

優點:

(1)分離介面和實現:客戶端從具體的產品實現解耦;

(2)使得切換產品族變得容易;

缺點:

不太容易擴充套件新的產品,如果需要給整個產品族新增一個新的產品,那麼就需要修改抽象工廠,這樣就會導致修改所有的工廠實現類;

2.建造者模式

建造者模式能夠將一個複雜的構建與其表示相分離,使得同樣的構建過程可以建立不同的表示。

與抽象工廠的區別:

(1)在建造者模式中,有一個指導者,由指導者來管理建造者,使用者是與指導者相互聯絡,指導者聯絡建造者,最後得到產品。即建造者模式可以強制實行一種分步驟進行的建造過程;

(2)建造者模式返回的是一個完成的複雜產品,而抽象工廠模式返回的是一系列相關的產品;

抽象工廠模式中,客戶端通過選擇具體工廠來生成所需的物件,而在建造者模式中,客戶端通過具體建造者型別並指導Director類如何去生成物件,側重於一步步構造一個複雜物件,然後將結果返回;

如果將抽象工廠模式看成是一個汽車配件生產廠,生成不同型別的汽車配件,那麼建造者模式就是一個汽車組裝廠,通過對配件進行組裝,返回一輛完整的汽車;

【建造者模式和工廠模式的區別】

建造者模式與工廠模式有點類似,不過關注點不同。工廠模式往往只關心你要的是什麼,而不關心這個東西的具體細節是什麼。而建造者模式則關心的是這個東西的具體細節的建立。

比如建立任務,我們關心的不僅是建立一個任務,還要關心他的性別、膚色和名字,則可以使用建造者模式。

【建造者模式的使用場景】

(1)需要生成的產品有複雜的內部結構;

(2)需要生成的產品物件的屬性相互依賴;

(3)在物件的建立過程中會使用到其他的物件;

【建造者模式的結構】

(1)Builder(抽象建造者):

該介面中一般有兩類方法:一類方法是buildPartX(),它們用於建立複雜物件的各個部件;

另一類方法是getResult():它們用於返回複雜物件。Builder既可以是抽象類,也可以是介面;

(2)ConcreteBuilder(具體建造者):

實現Builder介面

(3)Product(產品角色):

它是被構建的複雜物件,具體建造者建立該產品內部並定義它的裝配過程;

(4)Director(指揮者):

它負責安排複雜物件的建造次序,指揮者與抽象建造者之間存在關聯關係,可以在其建造方法construc()中呼叫建造者物件的部件構造與裝配方法,完成建造複雜物件的任務。

客戶端一般只需要和指揮者進行互動,在客戶端確定具體建造者型別,並例項化具體建造者物件(也可以通過配置檔案和反射機制),然後通過指揮者類的建構函式或者setter方法將該物件傳入指揮者類中;

【複雜物件定義】

複雜物件是指那些包含多個成員屬性的物件,這些成員屬性也稱為部件或者零件;

例如汽車:包括方向盤、發動機、輪胎等部件;

電子郵件:包括髮件人、收件人、主題、內容、附件等部件;

Builder builder = new ConcreteBuilder();//具體的建造者

Director director = new Director();//指揮者

director.order(builder);//指揮者指揮具體的建造者實施建造

Product product = builder.getProduct();//產品

【對建造者模式的總結】

(1)建造者模式的優點:

1.封裝性:使用建造者模式可以使客戶端不必知道產品內部的細節

2.建造者獨立,容易擴充套件:不同型別的產品建造者之間相互獨立,對系統的擴充套件非常有利;

3.便於控制細節風險:因為具體的建造者是相互獨立的,因此可以對建造過程逐步的細化,而不對其他的模組產生任何的影響;

(2)建造者模式的使用場景:

1.相同的方法,不同的執行順序,產生不同的事件結果時,可以採用建造者模式

2.多個部件或者零件,都可以裝配到一個物件中,但是產生的執行結果又不相同的時候,可以使用該模式;

3.產品類非常的複雜,或者產品類中的呼叫順序不同產生了不同的效能,這個時候使用建造者模式是非常合適的;

4.在物件建立過程中會使用到系統中的一些其他的物件,這些物件在產品物件的建立過程中不易得到的時候,也可以採用建造者模式封裝該物件的建立過程。

這種場景只能是一個補償的方法;

建造者模式關注的是零件型別和裝配工藝(順序),這是它與工廠方法模式最大不同的地方,雖然同為建立者模式,但是重點是不同的;

3.工廠方法模式

簡單工廠模式最大的缺點就是不滿足:開閉原則;因為每次增加介面的實現類的時候,需要修改工廠中的相關方法;

工廠方法模式和簡單工廠模式最大的不同之處在於:簡單工廠模式只有一個(對於一個專案或者一個獨立的模組而言)的工廠類,而工廠方法模式有一組實現了相同介面的工廠類;

工廠方法模式是類的建立模式:又叫虛擬構造器(Virtual Constructor)模式或者多型性工廠(Polymorphic Factory)模式。其用意是定義一個建立產品物件的工廠介面,將實際工作推遲到子類中;使這種工廠方法模式可以用來允許系統在不修改具體工廠角色的情況下引進新的產品;——抽象工廠對應抽象產品具體工廠對應具體產品

角色分類:

(1)抽象工廠角色:

(2)具體工廠角色:

(3)抽象產品角色:

(4)具體產品角色:

4.原始模型模式(原型模式)

通過一個原型物件,拷貝出多個一模一樣的物件,這個模式稱為原型模式;

原型模式(Property Pattern)是指使用原型例項來指定建立物件的種類,並且通過拷貝這些原型建立新的物件。原型模式是一種物件建立模式;

【原型模式中的角色】

(1)Property(抽象原型類):

宣告拷貝方法;

(2)ConcreteProperty(具體原型類):

實現在抽象原型類中宣告的拷貝方法,在拷貝方法中返回自己的一個拷貝物件;

(3)Client(客戶類):

讓一個原型物件拷貝自身,從而建立一個新的物件。

在客戶類中只需要直接例項化或者通過工廠方法等方式建立一個原型物件,再通過呼叫該物件的拷貝方法,即可得到多個相同的物件。

由於客戶類是針對抽象原型類Property程式設計,因此使用者可以根據需要選擇具體原型類,系統具有較好的可擴充套件性,增加或者更換具體原型類都很方便;

【實現拷貝的方法】

(1)通用實現方法:

通用的拷貝的實現是在具體的原型類的自定義拷貝非返回總例項化一個與自身型別相同的物件並將其返回,並將相關的屬性和引數傳入到新建立的物件中,保證它們的成員屬性相同;

(2)Java語言提供的clone()方法:

Object類中提供了一個clone方法可以將一個Java物件複製一份;

注意:能夠實現拷貝的Java類必須實現一個標識介面Cloneable,表示這個Java類支援被複制。如果一個類沒有實現這個介面但是呼叫了clone方法,會丟擲異常;

實現步驟:

1》在派生類中覆蓋clone()方法,並宣告為public

2》在派生類的clone()方法中,呼叫super.clone()

3》派生類需要實現Cloneable介面;

Java語言中的clone方法滿足如下關係:

1》對於任何的物件x,都有x.clone()!=x,即拷貝物件與原型物件不是同一個物件;

2》對於任何物件x,都有x.clone().getClass()==x.getClass(),型別相同;

3》如果物件x的equals方法定義恰當,那麼x.clone().equals(x)應該成立;

【原型模式淺拷貝和原型模式深度拷貝】

《淺拷貝》:

基本資料型別會被拷貝,而引用資料型別只是拷貝的地址,與原來的物件公用一個引用型別的物件;

當使用Object類中的clone方法時,只有滿足如下兩個條件的物件才不會被拷貝:

(1)類的成員變數,而不是方法內的變數;

(2)必須是一個物件,而不是一個原始型別;

《深拷貝》:

被拷貝物件的所有的基本型別的變數和原物件的值相同,對引用型別的變數自己也重新擁有了一份拷貝,與原物件的引用型別變數不再有聯絡;

深度拷貝的實現方式:

方式一:讓引用型別的變數繼續呼叫clone方法進行拷貝,然後將拷貝的值賦值給已經clone的類物件的屬性即可;

方式二:可以通過寫二進位制流來操作物件,然後實現物件的深度拷貝;

注意:並不是所有的物件都能夠實現深度克隆的,例如StringBuffer沒有過載Clone方法,而寫StringBuffer類還是一個final類,所以不能自動的實現深度克隆;

在Spring中,深度克隆的應用非常廣泛,在實體模型上面有這樣一個標註:

例如:

@Component(“lawsuitArchive”) @scope(“prototype”)

表明在別的地方引用的時候,是引用其原型,同時Entity也要實現Cloneable介面和複寫clone方法。因為原型在初始化的時候就已經建立了一部分有價值的東西,減少了容器的壓力,當我們在例如action中引用該物件的時候,直接可以使用@Resource(name=” lawsuitArchive”)引用物件,Spring會自動的裝配好我們想要的物件;

【使用序列化的方式實現深度拷貝】

在Java語言中,如果需要實現深度拷貝,可以通過序列化(Serialization)等方式來實現。序列化就是將物件寫到流的過程,寫到流中的物件那個是原物件的一個拷貝,而原物件仍然存在於記憶體中。通過序列化實現的拷貝不僅可以複製物件本身,而且可以複製其引用的成員物件,因此可以通過序列化將物件寫入到一個流中,再從流中將其讀取出來,可以實現深度拷貝。

注意:能夠實現序列化的物件必須實現Serializable介面,否則無法實現序列化操作;

//將物件寫入流中

      ByteArrayOutputStream bao = new ByteArrayOutputStream();

      ObjectOutputStream oos = new ObjectOutputStream(bao);

      oos.writeObject(this);

      //將物件從流中讀出

ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());

      ObjectInputStream ois = new ObjectInputStream(bis);

      ois.readObject();

【原型模式的總結】

【優點】:

(1)效能優良:

原型模式是記憶體中的二進位制流的拷貝,要比直接new一個物件效能好很多,特別是要在一個迴圈體內產生大量的物件的時候,原型模式可以更好的體現其優點;

(2)逃避建構函式的約束(既是優點也是缺點)

直接在記憶體中拷貝,建構函式是不會被執行的;

【原型模式的使用場景】

(1)類的初始化會消耗非常多的資源(資料、硬體資源等);

(2)效能和安全要求的場景:

通過new產生一個物件需要非常頻繁的資料準備或者訪問許可權,則可以使用原型模式;

(3)一個物件多個修改者的場景;

Obeject類的clone方法的原理是從記憶體中(具體的說是堆記憶體)以二進位制流的方式拷貝,重新分配一個記憶體塊,那建構函式沒有被執行是很正常的;

5.單例模式

單例模式的功能時候保證這個類在執行期間只會被建立一個單例項;

【單例模式的範圍】

目前Java裡面實現的單例是一個ClassLoader以及其子ClassLoader的範圍。

這就說明如果一個虛擬機器中有多個ClassLoader,而且這些ClassLoader都裝載某個類的話,就算這個類使用了單例模式,它也會產生很多個例項。如果一個機器上裝有多個虛擬機器,那麼每個虛擬機器裡面都應該至少有一個這個類的例項,也就是說整個機器上就有很多個例項。

注意:這裡討論的單例模式並不適用於叢集環境。

  • 懶漢式單例:在類載入的時候不建立單例例項,只有在第一次請求例項的時候建立;
  • 餓漢式單例:類被載入的時候,唯一例項已經被建立
  • 登記式單例

登記式單例實際上維護的是一組單例類的例項,將這些例項存放在一個Map(登記簿)中,對於已經登記過得例項,則從工廠直接返回,對於沒有登記的,則先登記,而後返回。

【雙重檢查加鎖】

public class Singleton{

         private volatile static Singleton instance = null;

         private Singleton(){}

         private static Singleton getInstance(){

         //先檢查例項是否存在,如果不存在才進入下面的程式碼塊

         if(instance==null){

                   //同步塊,執行緒安全的建立例項

                   synchronized(Singleton.class){

//再次檢查例項是否存在,如果不存在,才真正的建立例項

                   if(instance==null){

                   instance = new Singleton();

}

}

}

return instance;

}

}

所謂的“雙重檢查加鎖”機制,是指並不是每次進入getInstance方法都需要同步,而是先不同步。當進入方法之後,先檢查例項是否存在,如果不存在才進入下面的同步塊,這是第一重檢查,進入到同步塊過後,再次檢查例項是否存在。如果不存在,就在同步的情況下建立一個例項,這是第二重檢查。這樣一來,整個同步過程只需要一次同步,從而減少了多次在同步情況下進行判斷所浪費的時間。

在使用“雙重檢查加鎖”機制實現時,需要使用關鍵字 volatile,含義是被volatile修飾的變數的值不會被本地執行緒快取,所有對該變數的讀寫都是直接操作共享記憶體,從而確保多個執行緒能夠正確的處理該變數。

這種實現方式不僅可以實現執行緒安全的建立例項,而且又不會對效能造成太大的影響。它只是在第一次建立例項的時候需要同步,以後就不需要同步了,從而加快了執行速度。

但是因為volatile關鍵字可能會遮蔽掉虛擬機器中的一些必須的程式碼優化,所以執行效率並不是很高,如果沒有特別的需要,不要使用volatile關鍵字;

【既能夠實現延遲載入,又提供執行緒安全的單例模式】

public class Singleton {

   private Singleton(){}

   private static class SingletonHolder{

      //靜態初始化器,由JVM來保證執行緒安全

      private static Singleton instance = new Singleton();

   }

   public static Singleton getInstance(){

      return SingletonHolder.instance;

   }

}

解決方案:Lazy initialization holder class 模式,這個模式綜合使用了Java的類級內部類和多執行緒默認同步鎖的知識,很巧妙地同時實現了延遲載入和執行緒安全;

類級的內部類(靜態的成員式內部類)該內部類的例項與外部類的例項沒有繫結關係,而且只有被呼叫到才會裝載,從而實現了延遲載入;

這樣,當getInstance方法第一次被呼叫的時候,它第一次讀取SingletonHoler.instance,導致SingletonHolder類得到初始化;而這個類在裝載並被初始化的時候,會初始化它的靜態域,從而建立Singleton的例項,由於是靜態的域,因此只會被JVM在裝載類的時候初始化一次,並由JVM來保證它的執行緒安全性;

這個模式的優勢在於,getInstance方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問的開銷;

【單例和列舉】

單元素的列舉型別已經成為實現Singleton的最佳方法。Java的列舉型別實質上是功能齊全的類,因此可以有自己的屬性和方法。Java列舉型別的基本思想是通過公有的靜態final域為每個列舉常量匯出例項的類。從某個角度來講,列舉是單例的泛型化,本質上是單元素的列舉。

public enum Singleton {

   //定義一個列舉元素,它只代表了Singleton的一個例項

   uniqueInstance;

   //示意方法,單例可以有自己的操作

   public void singletonOperation(){

   }

}

由此可見,使用列舉來實現單例項控制,會更加的簡潔,而且無償的提供了序列化的機制,並由JVM從根本上提供保障,絕對防止多次例項化;

【單例模式的優點】

(1)減少記憶體的消耗,特別是一個物件如果需要頻繁的被建立,銷燬,而且建立或者銷燬時候效能又無法優化,這時候就適合採用單例模式

(2)減少系統性能開銷,(在 JavaEE中採用單例模式的時候,需要注意JVM垃圾回收機制)

(3)避免對資源的多重佔用

(4)單例模式可以在系統設定全域性的訪問點,優化共享資源的訪問,例如可以設計一個單例類,負責所有的資料表的對映處理;

【單例模式的缺點】

(1)單例模式沒有介面,擴充套件困難;

(2)單例模式對測試是不利的,在並行開發環境中,如果單例模式沒有完成,是不能完成測試的,沒有介面也不能使用mock的方式虛擬一個物件;

(3)單例模式與單一職責模式有衝突。

【單例模式使用場景】

(1)要求生成唯一序列號的環境

(2)在整個專案中需要有訪問一個共享訪問點或者共享資料,例如一個Web頁面上的計數器,可以不用每次重新整理都記錄到資料庫中,使用單例模式來保持計數器的值,並確保是執行緒安全的;

(3)建立一個物件消耗的資源過多,如要訪問i/O、訪問資料庫等資源

(4)需要定義大量的靜態常量和靜態方法(如工具類)的環境,可以採用單例模式。當然,也可以直接宣告為static的方式;

6.介面卡(變壓器)模式

一般就是定義介面卡繼承現有的類並且實現特定功能的介面來達到想要的效果;

背景:

為什麼要使用介面卡模式,直接修改原始碼不就可以了嗎?

其實介面卡是為了某種目的而為一個源類暫時性的加上某種方法,所以不能破壞原類的結構。同時不這麼做也符合Java的高內聚、低耦合的要求。既然

【介面卡模式的分類】

(1)類的介面卡模式,採用繼承來實現;

面向類的介面卡模式,這類介面卡模式就是主要用於單一的為某個類而實現適配的這樣一種模式。

(2)物件介面卡模式,採用物件組合的方式實現;

物件介面卡模式是把“源”作為一個物件聚合到介面卡類中。

【介面卡模式中的角色】

(1)目標抽象角色(Target):定義客戶所期待要使用的介面,在這裡可以抽象出來一個符合使用者需求的類;

(2)原角色(Adaptee):需要被適配的介面。是指現成的但是不太適合的、需要被改造的介面或類;

(3)介面卡角色(Adapter):用來把源介面轉換成符合要求的目標介面的裝置;

(4)客戶端(Client):這裡指的是目前的專案;

也就是說:通過介面卡對原角色進行改造(並不是改造原角色的原始碼)成目標抽象角色,最後返回給客戶端;

【介面卡模式的使用場景】

(1)系統想要使用現有的類,而此類的介面不符合系統的需要;

(2)想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。這些源類不一定有很複雜的介面;

針對物件介面卡來說,在設計中需要改變多個已有子類的介面;

如果使用類的介面卡模式,就要針對每一個子類做一個介面卡,不太實際;

7.橋樑模式

橋樑模式是一個非常重要的模式,也是比較複雜的一個模式。熟悉這個模式對於立即面向物件的設計原則,包括“開——閉”原則(OCP)以及組合/聚合複用原則(CARP)很有幫助。

橋樑模式的用意:將抽象化(Abstraction)與實現(Implementation)脫耦,使得二者可以獨立的變化;

(1)抽象化

存在於多個實體中的共同的概念性聯絡,就是抽象化。作為一個過程,抽象化就是忽略掉一些資訊,從而把不同的實體當做同樣的實體來對待;

(2)實現

即針對抽象化給出的具體實現;

(3)脫耦:

耦合是指兩個實體行為的某種強關聯。如果將它們之間的強關聯改換成弱關聯。因此,橋樑模式中的所謂脫耦,就是指在一個軟體系統的抽象化和實現之間使用組合

【橋樑模式的結構】

橋樑模式是物件的結構模式,又稱為柄體(Handle and Body)模式或者介面(Interface模式。

【橋樑模式中的角色】

(1)抽象化角色(Abstraction):

         抽象化給出的定義,並儲存一個對實現化物件的引用;

(2)修正抽象化角色(RefinedAbstraction):

         擴充套件抽象化角色,改變和修正父類對抽象化的定義;

(3)實現化角色(Implementor):

         這個角色給出實現化角色的介面,但是不給出具體的實現。必須指出的是,這個介面不一定與抽象化角色的介面定義相同,實際上,這兩個介面可以非常不一樣。實現化角色應當只給出底層操作,而抽象化角色應當只給出基於底層操作的更高一層的操作;

(4)具體實現化(ConcreteImplementor)角色:

給出實現化角色介面的具體實現;

【角色之間的關聯】

在橋樑模式的4個角色中,抽象化角色就像是一個水杯的手柄,而實現化角色和具體的實現化角色就像是水杯的杯身,手柄控制杯身,這就是該模式別名“柄體”的來源;

抽象化等級結構中的方法通過向對應的實現化物件的委派實現自己的功能,這意味著抽象化角色可以通過向不同的實現化物件委派,來達到動態的轉換自己的功能的目的;

一般來講,實現化角色中的每個方法都應當有一個抽象化角色中的某一個方法與之對應,但是反過來不一定;換言之,抽象化角色的介面比實現化角色的介面寬。抽象化角色除了提供與實現化角色相關的方法之外,還有可能提供其他的方法;而實現化角色則往往僅為實現抽象化角色的相關行為而存在;

【使用橋樑模式的場景】

如果一個介面或者類的抽象和實現是混雜在一起的,這樣就導致了一個維度的變化會引起另一個維度進行相應的變化,從而使得程式擴充套件起來非常的困難;

如果想要解決這個問題,就必須把這兩個維度分開,也就是將抽象部分實現部分分開,讓他們相互獨立,這樣就可以實現獨立的變化,使擴充套件變得簡單;

【橋樑模式在Java中典型應用】

橋樑模式在Java中的典型應用是實現JDBC驅動器。JDBC為所有的關係型資料庫提供了一個通用的介面。一個應用系統動態地選擇一個合適的驅動器,然後通過驅動器向資料庫引擎發出指令。這個過程就是將抽象角色的行為委派給實現角色的過程;

抽象角色可以針對任何的的資料庫引擎發出查詢指令,因為抽象角色並不直接與資料庫引擎打交道,JDBC驅動器負責這個底層的操作。

JDBC模式的架構把抽象部分和具體的部分分離開來,從而使得抽象部分和具體部分都可以獨立的擴充套件。對於引用程式而言,只要選用不同的驅動,就可以讓程式操作不同的資料庫,而不需要更改應用程式,從而實現在不同資料庫上移植;

對於驅動程式而言,為資料庫實現不同的驅動程式,並不會影響應用程式;

總結:

基於JDBC的應用程式使用JDBC的API,相當於是對資料庫操作的抽象的擴充套件,算作是橋樑模式的抽象部分;而具體的介面實現是由驅動來完成的,驅動這邊自然就相當於橋樑模式中的實現部分了。而橋接的方式,不再是讓抽象部分持有實現部分,而是採用類似於工廠的做法,通過DriverManager來把抽象部分和實現部分對接起來,從而實現抽象部分和實現部分解耦;

【橋樑模式的詳解】

橋接的概念:

         所謂的橋接,是指在不同的東西之間搭一個橋,讓它們能夠連線起來,可以相互通訊和使用。具體就是在被分離的抽象部分和實現部分之間來搭一個橋;

注意:在橋樑模式中橋接是單向的,也就是隻能是抽象部分的物件去使用具體實現部分的物件,而不能反過來;

(1)為什麼需要橋接:

為了達到讓抽象部分和實現部分都可以獨立變化的功能,在橋樑模式中,是把抽象部分和實現部分分離開來的;

通過搭橋,讓抽象部分通過這個橋就可以呼叫到實現部分的功能了,因此需要橋接;

(2)如何橋接:

只需要讓抽象部分擁有實現部分的介面物件,就算橋接上了。在抽象部分可以通過這個介面來呼叫具體實現部分的功能。

具體可以通過在抽象部分的類的建構函式中引入實現部分的物件也可以通過setter方法設定實現部分的物件;

(3)獨立變化:

橋樑模式的意圖就是:使得抽象和實現可以獨立的變化,都可以分別得擴充。也就是說,抽象部分和實現部分是一種非常鬆散的關係,從某個角度來講,抽象部分和實現部分是可以完全分開的、獨立的,抽象部分不過是一個使用實現部分對外介面的程式罷了;

(4)動態變換功能:

由於橋樑模式中的抽象部分和實現部分是完全分離的,因此可以在執行時候動態的組合具體的真實實現,從而達到動態變換的目的;

即同一個真實實現可以被不同的抽象物件使用,反過來,同一個抽象也可以有多個不同的實現;

(5)橋樑模式和繼承

繼承是擴充套件物件的一種常見手段。通常情況下,繼承擴充套件的功能 變化維度都是一維的,也就是變化的因素只有一類。對於出現變化因素有兩類的,也就是有兩個變化維度的情況,繼承實現就會比較痛苦。

從理論上來講,如果使用繼承的方式來實現這種有兩個維度變化的情況,最後實際的實現類應該是兩個維度上可變數量的乘積那麼多個。

其實橋樑模式主要是把繼承改成了使用物件組合,從而把兩個維度分開了,讓每一個維度去單獨的變化,最後通過物件物件組合的方式,把兩個維度組合起來,每一種組合的方式就相當於原來繼承中的一種實現,這樣就有效的減少了實際實現的類的個數;

從理論上來講,如果用橋樑模式的方式來實現這種有兩個變化維度的情況,最後實際的實現類應該是兩個維度上可變數量的和那麼多個。

【誰來橋接的問題】

就是誰來負責抽象部分和實現部分的關係。

也就是誰來負責建立實現部分物件,並且把它設定到抽象部分的物件裡面去。

這點對於橋樑模式來講,非常的重要,大致如下:

(1)由客戶端負責建立Implementor物件,並在建立抽象部分的時候,把它設定到抽象部分的物件裡面去;

(2)可以在抽象部分的物件構建的時候,由抽象部分的物件自己來建立相應的Implementor的物件,當然可以給它傳遞一些引數,它可以根據引數來選擇並建立具體的Implementor的物件;

(3)可以在Abstraction中選擇並建立一個預設的Implementor的物件,然後子類可以根據需要改變這個實現;

(4)也可以使用抽象工廠或者是簡單工廠來選擇並建立具體的Implementor的物件,抽象部分的類可以通過呼叫工廠的方法來獲取Implementor的物件;

(5) 如果使用IoC/DI容器的話,還可以通過IoC/DI 容器來建立具體的Implementor的物件,並注入回到Abstraction中;

【廣義橋接】

當使用Java編寫程式的時候,一個很重要的原則就是“面向介面程式設計”,更準確點就是“面向抽象程式設計”。介面把具體的實現與使用介面的客戶程式分離開來,從而使得具體的實現和使用介面的客戶程式可以分別擴充套件,而不會相互影響。

從廣義的橋接模式的角度來看,廣為熟悉的三層架構其實就是在組合使用橋接模式。橋接模式是可以連續組合使用的,一個橋樑模式的實現部分可以作為下一個橋樑模式的抽象部分。

如果從更本質的角度來看,基本上只要是面向抽象編寫的Java程式,都可以視為橋樑模式的應用,都是讓抽象和實現相分離,從而使得它們可以獨立變化;

【橋樑模式的總結】

橋樑模式的本質是分離抽象和實現。

橋樑模式最重要的工作就是分離抽象部分和實現部分,這是解決問題的關鍵。

只有把抽象和實現分離開了,才能夠讓它們可以獨立的變化;只有從抽象和實現可以獨立的變化,系統才會有更好的可擴充套件性、可維護性;

【橋樑模式的優點】

(1)很好的實現了開閉原則;

在使用橋樑模式的時候,通常情況下,頂層的Abstraction和Implementor是不變的,而具體的Implementor的實現,是可變化的,由於Abstraction是通過介面來操作具體的實現,因此具體的Implementor的實現是可以擴充套件的,根據需要可以有多個具體的實現;

(2)多用物件組合、少用物件繼承;

如果使用物件繼承來擴充套件功能,不但會使得物件之間有很強的耦合性,而且會需要很多的子類來完成相應的功能,需要維護N個維度上可變化數量的乘積的個數的子類;

         而採用物件的組合,鬆散了物件之間的耦合性,大大減少了子類的個數,大約需要N個維度的可變化數量之和的子類;

(3)分離抽象和實現部分

(4)更好的擴充套件性;

(5)可動態的切換實現;

(6)可以減少子類的個數;

8.組合模式(合成模式)

組合(Composite)模式體現了整體與部分的關係,其典型的應用就是樹形結構。組合涉及的是一組物件,其中有的物件可能含有其他的物件,因此有的物件可能代表一個物件群組,而有的則就是單個物件,即葉子(leaf)。

定義:將物件組合成樹形結構以表示“整體-部分”的層次結構。Composite模式使得單個物件和組合物件的使用具有一致性;

組合模式中兩個重要的建模概念:

(1)所涉及的群組既要能包含單個個體,還要能包含其他的群組。一個常見的錯誤是所設計的群組只能包含葉子;

(2)要定義出單個物件和物件組合的公共特性;

【組合模式中主要的構成元素】

(1)Component類:組合中的物件宣告介面,在適當的情況下,實現所有類共有介面的行為。宣告一個介面用於訪問和管理Component的子部件;

(2)Leaf類:葉節點物件。葉節點行為用來儲存葉節點集合;

(3)Composite類:實現Component的相關操作,比如Add或remove操作;

(4)children:用來儲存葉節點集合;

【組合模式涉及到如下幾個角色】

(1)抽象構件(Component)角色:

         這是一個抽象角色,它給參加組合的物件規定一個介面。這個介面給出共有的介面以及其預設的行為;

(2)樹葉構件(Leaf)角色:

代表參加組合的樹葉物件。

(3)樹枝構件(Composite)角色:

代表引數組合的所有的子物件的物件,並給出樹枝構建物件的行為;

【組合模式的使用範例】

組合模式在程式設計中有著廣泛的應用,比如Dom4j、資源管理器、Java GUI容器層次圖等都是組合模式應用的典範。組合模式需要分析思考才能鑑別出來。

9.裝飾模式

裝飾模式是指給一個類新增一些額外的職責,並且在新增這些額外的職責的時候不會控制該類的執行邏輯。

【裝飾模式的角色】

(1)抽象構件角色(Component):給出一個抽象的介面,以規範準備接受附加責任的物件。相當於I/O流中的InputStream/OutputStream和Reader/Writer

(2)具體的構件角色(ConcreteComponent):定義一個將要接受附加責任的類,相當於I/O流中的FileOutputStream和FileInputStream

(3)裝飾角色(Docorator):持有一個抽象構件(Component)角色的引用,並定義一個與抽象構件一致的介面。相當於I/O 流中的FilterOutputStream和FilterInputStream

(4)具體裝飾角色(ConcreteDecorator):負責給構建物件“貼上”附加的職責。相當於I/O流裡面的BufferedOutputStream、BufferedInputStream、DataOutputStream和DataInputStream

【裝飾模式使用場景距離】

1》獎金計算面臨的問題:

計算邏輯複雜;

需要有足夠的靈活性,可以方便的增加或者減少功能;

要能動態組合計算方式,不同的人蔘與的計算不同;

實現:

為了提高靈活性,可以把多種計算獎金的方式分散到不同的裝飾器物件裡面。然後採用動態組合的方式,給基本的計算獎金的物件新增計算獎金的功能,這樣,每個裝飾器相當於計算獎金的一個部分。這種方式要比為基本的計算獎金的物件增加子類來的更加靈活。。為了達到一層層組裝的效果,裝飾模式還要求裝飾器要實現與被裝飾物件相同的業務介面,這樣才能以同一種方式一次組合下去;

【面向物件的設計規則】

儘量使用物件組合,而不是物件繼承;

【Java中裝飾者模式的應用】

1》最經典的應用就是I/O流;

2》裝飾模式和AOP

從某個側面來講,裝飾模式和AOP要實現的功能是類似的,只不過AOP的實現方法不同,會更加的靈活,更加的可配置;

【裝飾者模式的總結】

(1)比繼承更加的靈活,繼承是靜態的,而且一旦繼承之後,是所有的子類都有同樣的功能。

而裝飾者模式則把功能分離到每個裝飾器中,然後通過物件組合的方式,在執行時動態的組合功能,每個裝飾的物件最終有哪些功能,是由執行期動態組合的功能來決定;

(2)更容易複用功能:

裝飾者模式將一系列的複雜的功能分散到每個裝飾器中,一般一個裝飾器只實現一個特定的功能。

(3)簡化高層定義

裝飾模式可以通過組合裝飾器的方式,給物件新增任意多的功能,因此在進行高層定義的時候,不用把所有的功能都定義出來,而是定義最基本的功能就可以了,可以在使用需要的時候,組合相應的裝飾器來完成需要的功能;

(4)會產生很多細粒度物件

裝飾模式是把一系列複雜的功能分散到每個裝飾器中,一般一個裝飾器只實現一個功能,這樣會產生很多的細粒度物件,而且功能越複雜,所需要的細粒度物件就越多;

10.外觀模式(門面模式)

外觀模式也被稱為Façade模式,能夠為子系統中的一組介面提供一個統一介面。Façade模式定義了一個更高層的介面,使子系統更加容易的使用。外觀模式是一種結構型模式,它主要解決的問題是:元件的客戶和元件中各種複雜的子系統有了過多的耦合,隨著外部客戶程式和各子系統的煙花,這種過多的耦合面臨很多變化的挑戰。

外觀模式也是由代理模式發展而來的。與代理模式類似,代理模式是一對一的代理,而外觀模式是一對多的代理。與裝飾者模式不同的是,裝飾模式為物件增加功能,而外觀模式則是提供一個簡化的呼叫方式。一個系統可以有多個外觀類(門面類),每個門面類都只有一個例項,可以使用單例模式來實現;

【門面模式的角色】

1》門面角色facade:門面模式的核心,被客戶角色呼叫,因此它熟悉子系統的功能。它內部根據客戶的需求預定了幾種功能的組合;

2》子系統角色:實現了子系統的功能。

3》客戶角色:呼叫façade角色來完成要得到的功能

【外觀模式的意義】

外觀模式屬於結構型模式,其意圖是為子系統中的一組介面提供一個一致的介面,Façade模式定義了一個高層介面,這個介面使得這一子系統更加容易使用;

在專案設計中,將一個系統劃分為若干個子系統有利於降低系統的複雜度,一個常見的設計目標是使子系統之間的通訊和相互依賴的關係達到最小,實現該目標的途徑之一就是引入一個外觀物件,它為子系統中較一般的設施提供了一個單一而簡單的介面;

【外觀模式的實用性】

1》當需要為一個複雜的子系統提供一個簡單的介面的時候,子系統往往因為不斷地演化而變得越來越複雜,通常在使用模式的時候都會產生更多、更小的類,這使得子系統更具有可重用性,也更容易對子系統進行定製;

2》客戶程式與抽象類的實現部分之間存在很大的依賴性,引入Façade將這個子系統與客戶以及其他的子系統進行分離,可以提高子系統的獨立性和可移植性;

3》當需要構建一個層次結構的子系統時,使用Façade模式定義子系統中每層的入口點,如果子系統之間是相互依賴的,就可以讓它們通過Façade進行通訊,從而簡化它們之間的依賴關係;

【外觀模式的總結】

外觀模式的使用場景:

(1)為一個子系統提供一個簡單的介面;

(2)提高子系統的獨立性;

(3)在層次化結構中,可以使用Façade模式定義系統中每一層的入口;

外觀模式可以將一些複雜的類包裝成一個簡單的封閉介面。外觀模式對客戶遮蔽了複雜的子系統元件,併為一般使用者提供了一個比較簡單的程式設計介面。但是,它並沒有限制高階使用者在需要的時候使用深層次的、較複雜的類;

【外觀模式的優點】

(1)對客戶遮蔽子系統元件,因而減少了客戶處理的物件數目,從而使得子系統使用起來更加的方便;

(2)實現了子系統與客戶之間的鬆散耦合關係,而子系統內部的功能元件往往是緊耦合的。鬆耦合關係使得子系統的元件變化不會影響到其客戶;

(3)如果需要,也不限制使用子系統類;

11.享元模式

享元模式即FlyWeight模式,是構造型模式之一,通過與其他的類似物件共享資料來較少記憶體的佔用情況。享元模式運用共享技術有效的支援大量細粒度的物件,也就是說,在一個系統中如果有多個相同的物件,那麼只需要共享一份即可,而不必去例項化每一個物件。

在FlyWeight模式中,由於需要生產各種各樣的物件,所以在FlyWeight(享元)模式中常出現Factory模式。FlyWeight的內部狀態是用來共享的,FlyWeight Factory負責維護一個物件儲存池來存放內部狀態的物件。為了方便呼叫,FlyWeightFactory類一般使用Singleton模式實現。FlyWeight模式是一個提高程式效率和效能的模式,會大大的加快程式的執行速度;

【享元模式的結構】

享元模式採用一個共享來避免大量擁有相同內容物件的開銷。這種開銷最常見、最直觀的就是記憶體的消耗。享元模式能夠做到共享的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。

內蘊