設計模式——開發經常使用的設計模式梳理

. 基礎學習:UML四種關係

耦合度大小關係

泛化 = 實現 > 組合 > 聚合 > 關聯 > 依賴javascript

依賴(Dependency)

好好學java好好學java

一我的(Person)能夠買車(car)和房子(House),那麼就能夠稱:Person類依賴於Car類和House類
   這裏注意與下面的關聯關係區分:Person類裏並無使用Car和House類型的屬性,Car和House的實例是以參量的方式傳入到buy()方法中。
   依賴關係在Java語言中體現爲局域變量、方法的形參,或者對靜態方法的調用。php

關聯(Association)

好好學java好好學java

它使一個類知道另外一個類的屬性和方法。
   關聯能夠是雙向的,也能夠是單向的。
   在Java語言中,關聯關係通常使用成員變量來實現。java

聚合(Aggregation)

好好學java好好學java

聚合是關聯關係的一種,是強的關聯關係。
   聚合是總體和個體之間的關係,但個體能夠脫離總體而存在。
   例如,汽車類與引擎類、輪胎類,以及其它的零件類之間的關係便總體和個體的關係。
   與關聯關係同樣,聚合關係也是經過成員變量實現的。可是關聯關係所涉及的兩個類是處在同一層次上的,而在聚合關係中,兩個類是處在不平等層次上的,一個表明總體,另外一個表明部分。算法

組合(Composition)

好好學java好好學java

組合是關聯關係的一種,是比聚合關係強的關係,也以成員變量的形式出現。
   在某一個時刻,部分對象只能和一個總體對象發生組合關係,由後者排他地負責生命週期。
   部分和總體的生命週期同樣。
   總體能夠將部分傳遞給另外一個對象,這時候該部分的生命週期由新總體控制,而後舊總體能夠死亡。編程

. 策略模式

什麼是策略模式

一個類中的一些行爲,可能會隨着系統的迭代而發生變化。爲了使得該類知足開放-封閉原則(即:具有可擴展性 或 彈性),咱們須要將這些將來會發生動態變化的行爲從該類中剝離出來,並經過預測將來業務發展的方式,爲這些行爲抽象出共有的特徵,封裝在抽象類或接口中,並經過它們的實現類提供具體的行爲。本來類中須要持有該抽象類/接口的引用。在使用時,將某一個具體的實現類對象注入給該類所持有的接口/抽象類的引用。設計模式

類圖描述

好好學java好好學java

若是類A中有兩個行爲X和Y會隨着業務的發展而變化,那麼,咱們須要將這兩個行爲從類A中剝離出來,並造成各自的繼承體系(策略體系)。每一個繼承體系(策略體系)的頂層父類/接口中定義共有行爲的抽象函數,每一個子類/實現類中定義該策略體系具體的實現。安全

其中,每個被抽象出來的繼承體系被稱爲一個策略體系,每一個具體的實現類被稱爲策略。微信

此時,策略體系已經構建完成,接下來須要改造類A。
在類A中增長所需策略體系的頂層父類/接口,並向外暴露一個共有的函數action給調用者使用。多線程

在Spring項目中,策略類和類A之間的依賴關係能夠經過依賴注入來完成。架構

到此爲止,策略模式已經構建完成,下面咱們來看優缺點分析。

策略模式的優勢

1. 知足開放封閉原則

若是類A須要更換一種策略的時候,只需修改Spring的XML配置文件便可,其他全部的代碼均不須要修改。

好比,將類A的策略X_1更換成X_2的方法以下:

<bean id="a" class="類A">
   <!-- 將本來策略實現類X_1修改成策略實現類X_2便可 -->
   <property name="策略接口X" class="策略實現類X_2" />
</bean>

此外,若是須要新增一種策略,只須要爲策略接口X添加一個新的實現類便可,並覆蓋其中的commonAction函數。而後按照上面的方式修改XML文件便可。

在這個過程當中,在保持原有Java代碼不發生變化的前提下,擴展性了新的功能,從而知足開放封閉原則。

2. 可方便地建立具備不一樣策略的對象

若是咱們須要根據不一樣的策略建立多種類A的對象,那麼使用策略模式就能很容易地實現這一點。

好比,咱們要建立三個A類的對象,a、b、c。其中,a使用策略X_1和Y_1,b使用策略X_2和Y_2,c使用策略X_3和Y_3。
要建立這三個對象,咱們只需在XML中做以下配置便可:

<bean id="a" class="類A">
   <property name="策略接口X" class="策略實現類X_1" />
   <property name="策略接口Y" class="策略實現類Y_1" />
</bean>
<bean id="b" class="類A">
   <property name="策略接口X" class="策略實現類X_2" />
   <property name="策略接口Y" class="策略實現類Y_2" />
</bean>
<bean id="c" class="類A">
   <property name="策略接口X" class="策略實現類X_3" />
   <property name="策略接口Y" class="策略實現類Y_3" />
</bean>

答疑

問:如何實現部分繼承?也就是類Son1只繼承Father的一部分功能,Son2繼承Father的另外一部分功能。

這是設計上的缺陷,當出現這種狀況時,應當將父類再次拆分紅2個子類,保證任何一個父類的行爲和特徵均是該繼承體系中共有的!

好好學java好好學java

問:隨着需求的變化,父類中須要增長共有行爲時怎麼辦?這就破壞了「開放封閉原則」。

這並未破壞「開放封閉原則」!在系統迭代更新的過程當中,修改原有的代碼是在所不免的,這並不違背「開放封閉原則」。
「開放封閉原則」要求咱們:當系統在迭代過程當中,第一次出現某一類型的需求時,是容許修改的;在此時,應該對系統進行修改,並進行合理地設計,以保證對該類型需求的再次修改具有可擴展性。當再一次出現該類型的需求時,就不該該修改原有代碼,只容許經過擴展來知足需求。

. 觀察者模式

觀察者模式是什麼

若是出現以下場景需求時,就須要使用觀察者模式。

若是存在一系列類,他們都須要向指定類獲取指定的數據,當獲取到數據後須要觸發相應的業務邏輯。這種場景就能夠用觀察者模式來實現。

在觀察者模式中,存在兩種角色,分別是:觀察者和被觀察者。被觀察者即爲數據提供者。他們呈多對一的關係。

類圖描述

好好學java好好學java
  • 被觀察者是數據提供方,觀察者是數據獲取方

  • 一個普通的類,若是要成爲觀察者,獲取指定的數據,一共須要以下幾步:

    • 首先,須要實現Observer接口,並實現update函數;

    • 而後,在該函數中定義獲取數據後的業務邏輯;
      update(Observable, Object)一共有兩個參數:
        Observable:被觀察者對象(數據提供方)
        Object:數據自己

    • 最後,經過調用 被觀察者 的addObservable()或者經過Spring的XML配置文件完成觀察者向被觀察者的注入。此時,該觀察者對象就會被添加進 被觀察者 的List中。

  • 調用者纔是真正的數據提供方。當調用者須要廣播最新數據時,只需調用 被觀察者 的notidyObservers()函數,該函數會遍歷List集合,並依次調用每一個Observer的update函數,從而完成數據的發送,並觸發每一個Observer收到數據後的業務邏輯。

兩種註冊觀察者的方式

將Observer註冊進Observable中有以下兩種方式:

1. 運行前,經過Spring XML

在系統運行前,若是觀察者數量能夠肯定,並在運行過程當中不會發生變化,那麼就能夠在XML中完成List對象的注入,這種方式代碼將會比較簡潔。

一、配置好全部 觀察者 bean

<!-- 建立observerA -->
<bean name="observerA" class="ObservserA">
</bean>
<!-- 建立observerB-->
<bean name="observerB" class="ObservserB">
</bean>

二、 配置好 被觀察者 bean,並將全部觀察者bean注入給被觀察者bean

<!-- 建立observable -->
<bean name="observable" class="Observable">
   <property name="observerList">
       <list>
           <ref bean="observerA" />
           <ref bean="observerB" />
       </list>
   </property>
</bean>

2. 運行中,經過addObserver()函數

在Spring初始化的時候,經過addObserver()函數將全部Observer對象注入Observable的observerList中。

@Component
public class InitConfiguration implements ApplicationListener<ContextRefreshedEvent>{
   @Override
   public void onApplicationEvent(ContextRefreshedEvent arg0) {
       if(event.getApplicationContext().getParent() == null){
           Observable observable = (Observable)event.getApplicationContext().getBean("observable");
           ObserverA observerA = (ObserverA)event.getApplicationContext().getBean("observerA");
           ObserverB observerB = (ObserverB)event.getApplicationContext().getBean("observerB");
           observable.setObserverList(Arrays.asList(observerA, observerB));
       }  
   }
}

建議使用第一種方式初始化全部的觀察者,此外,被觀察者仍然須要提供addObserver()函數供系統在運行期間動態地添加、刪除觀察者對象。

JDK提供的觀察者模式工具包

JDK已經提供了觀察者模式的工具包,包括Observable類和Observer接口。若要實現觀察者模式,直接使用這兩個工具包便可。

. 裝飾模式

什麼時候使用

  • 須要加強一個對象中某些函數的功能。

  • 須要動態地給一個對象增長功能,這些功能能夠再動態地撤銷。

  • 須要增長 由一些基本功能排列組合 而產生的大量功能,從而使繼承體系大爆炸。

類圖描述

好好學java好好學java
在裝飾模式中的各個角色有:
  • 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。

  • 具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。

  • 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。

  • 具體裝飾(Concrete Decorator)角色:負責給構件對象」貼上」附加的責任。

Decorator中包含Component的成員變量,每一個Concrete Decorator實現類均須要實現operation()函數,該函數大體過程以下:

class ConcreteDecorator {
   private Component component;
   返回類型 operation(){
       // 執行上一層的operation(),並獲取返回結果
       返回結果 = component.operation();
       // 拿到返回結果後,再作額外的處理
       處理返回結果
       return 返回結果;
   }
}

使用裝飾類的過程以下:

// 準備好全部裝飾類
DecoratorA decoratorA = new DecoratorA();
DecoratorB decoratorB = new DecoratorB();
DecoratorC decoratorC = new DecoratorC();
// 準備好 被裝飾的類
Component component = new Component();
// 組裝裝飾類
decoratorC.setComponent(decoratorB);
decoratorB.setComponent(decoratorA);
decoratorA.setComponent(component);
// 執行
decoratorC.operation();

執行過程以下:

好好學java好好學java

優勢

一、Decorator模式與繼承關係的目的都是要擴展對象的功能,可是Decorator能夠提供比繼承更多的靈活性。繼承經過覆蓋的方式重寫須要擴展的函數,固然也能夠經過super.xxx()獲取本來的功能,而後在該功能基礎上擴展新功能,但它只能增長某一項功能;若是要經過繼承實現增長多種功能,那麼須要多層繼承多個類來實現;而Decorator模式能夠在原有功能的基礎上經過組合來增長新功能,這些新功能已經被封裝成一個個獨立的裝飾類,在運行期間經過搭積木的方式選擇裝飾類拼湊便可。

二、經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,設計師能夠創造出不少不一樣行爲的組合。

缺點

一、這種比繼承更加靈活機動的特性,也同時意味着更加多的複雜性。
二、裝飾模式會致使設計中出現許多小類,若是過分使用,會使程序變得很複雜。

三、裝飾模式是針對抽象組件(Component)類型編程。可是,若是你要針對具體組件編程時,就應該從新思考你的應用架構,以及裝飾者是否合適。固然也能夠改變Component接口,增長新的公開的行爲,實現「半透明」的裝飾者模式。在實際項目中要作出最佳選擇。

設計原則

  • 多用組合,少用繼承。
    利用繼承設計子類的行爲,是在編譯時靜態決定的,並且全部的子類都會繼承到相同的行爲。然而,若是可以利用組合的作法擴展對象的行爲,就能夠在運行時動態地進行擴展。

. 單例模式

Java中單例(Singleton)模式是一種普遍使用的設計模式。單例模式的主要做用是保證在Java程序中,某個類只有一個實例存在。一些管理器和控制器常被設計成單例模式。

單例模式有不少好處,它可以避免實例對象的重複建立,不只能夠減小每次建立對象的時間開銷,還能夠節約內存空間;可以避免因爲操做多個實例致使的邏輯錯誤。若是一個對象有可能貫穿整個應用程序,並且起到了全局統一管理控制的做用,那麼單例模式也許是一個值得考慮的選擇。

單例模式有不少種寫法,大部分寫法都或多或少有一些不足。下面將分別對這幾種寫法進行介紹。

. 餓漢模式

public class Singleton{  
   private static Singleton instance = new Singleton();  
   private Singleton(){}  
   public static Singleton newInstance(){  
       return instance;  
   }  
}  
  • 類的構造函數定義爲private,保證其餘類不能實例化此類;

  • 而後提供了一個靜態實例並返回給調用者;

  • 餓漢模式在類加載的時候就對實例進行建立,實例在整個程序週期都存在

  • 優勢:只在類加載的時候建立一次實例,不會存在多個線程建立多個實例的狀況,避免了多線程同步的問題。

  • 缺點:即便這個單例沒有用到也會被建立,並且在類加載以後就被建立,內存就被浪費了。

  • 適用場景:這種實現方式適合單例佔用內存比較小,在初始化時就會被用到的狀況。可是,若是單例佔用的內存比較大,或單例只是在某個特定場景下才會用到,使用餓漢模式就不合適了,這時候就須要用到懶漢模式進行延遲加載。

. 懶漢模式(存在線程安全性問題)

public class Singleton{  
   private static Singleton instance = null;  
   private Singleton(){}  
   public static Singleton newInstance(){  
       if(null == instance){  
           instance = new Singleton();  
       }  
       return instance;  
   }  
}  
  • 懶漢模式中單例是在須要的時候纔去建立的,若是單例已經建立,再次調用獲取接口將不會從新建立新的對象,而是直接返回以前建立的對象。

  • 若是某個單例使用的次數少,而且建立單例消耗的資源較多,那麼就須要實現單例的按需建立,這個時候使用懶漢模式就是一個不錯的選擇。

  • 可是這裏的懶漢模式並無考慮線程安全問題,在多個線程可能會併發調用它的getInstance()方法,致使建立多個實例,所以須要加鎖解決線程同步問題,實現以下。

. 懶漢模式(線程安全,但效率低)

public class Singleton{  
   private static Singleton instance = null;  
   private Singleton(){}  
   public static synchronized Singleton newInstance(){  
       if(null == instance){  
           instance = new Singleton();  
       }  
       return instance;  
   }  
}

加鎖的懶漢模式看起來即解決了線程併發問題,又實現了延遲加載,然而它存在着性能問題,依然不夠完美。synchronized修飾的同步方法比通常方法要慢不少,若是屢次調用getInstance(),累積的性能損耗就比較大了。

. 懶漢模式(線程安全,效率高)

public class Singleton {  
   private static Singleton instance = null;  
   private Singleton(){}  
   public static Singleton getInstance() {  
       if (instance == null) {
           synchronized (Singleton.class) {  
               if (instance == null) {
                   instance = new Singleton();  
               }  
           }  
       }  
       return instance;  
   }  
}

這種方式比上一種方式只多加了一行代碼,那就是在synchronized之上又加了一層判斷if (instance == null)。這樣當單例建立完畢後,不用每次都進入同步代碼塊,從而能提高效率。固然,除了初始化單例對象的線程ThreadA外,可能還存在少數線程,在ThreadA建立完單例後,剛釋放鎖的時候進入同步代碼塊,但此時有第二道if (instance == null)判斷,所以也就避免了建立多個對象。並且進入同步代碼塊的線程相對較少。

. 靜態內部類(懶漢+無鎖)

public class Singleton{  
   private static class SingletonHolder{  
       public static Singleton instance = new Singleton();  
   }  
   private Singleton(){}  
   public static Singleton newInstance(){  
       return SingletonHolder.instance;  
   }  
}  

這種方式一樣利用了類加載機制來保證只建立一個instance實例。它與餓漢模式同樣,也是利用了類加載機制,所以不存在多線程併發的問題。不同的是,它是在內部類裏面去建立對象實例。這樣的話,只要應用中不使用內部類,JVM就不會去加載這個單例類,也就不會建立單例對象,從而實現懶漢式的延遲加載。也就是說這種方式能夠同時保證延遲加載和線程安全。

. 枚舉

public enum Singleton{  
   instance;  
   public void whateverMethod(){}      
}

上面提到的四種實現單例的方式都有共同的缺點:

一、須要額外的工做來實現序列化,不然每次反序列化一個序列化的對象時都會建立一個新的實例。
二、可使用反射強行調用私有構造器(若是要避免這種狀況,能夠修改構造器,讓它在建立第二個實例的時候拋異常)。

而枚舉類很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器以外,還提供了自動序列化機制,防止反序列化的時候建立新的對象。所以,《Effective Java》做者推薦使用的方法。不過,在實際工做中,不多看見有人這麼寫。

. 模板方法模式

定義

在父類中定義算法的流程,而算法的某些沒法肯定的細節,經過抽象函數的形式,在子類中去實現。

也能夠理解爲,一套算法的某些步驟可能隨着業務的發展而改變,那麼咱們能夠將肯定的步驟在父類中實現,而可變的步驟做爲抽象函數讓其在子類中實現。

  • 在模板方法模式中,父類是一個抽象類,算法的每一步都被封裝成一個函數,templateMethod函數將全部算法步驟串聯起來。

  • 對於不變的步驟,用private修飾,防止子類重寫;

  • 對於可變的步驟,用abstract protected修飾,必需要求子類重寫;

  • 子類重寫完全部抽象函數後,調用templateMethod便可執行算法。

. 外觀模式

外觀模式這種思想在項目中廣泛存在,也極其容易理解,你們必定用過,只是沒有上升到理論的層面。這裏對這種思想進行介紹。

外觀模式他屏蔽了系統功能實現的複雜性,向客戶端提供一套極其簡單的接口。客戶端只須要知道接口提供什麼功能,如何調用就好了,不須要管這些接口背後是如何實現的。從而使得客戶端和系統之間的耦合度大大下降,客戶端只需跟一套簡單的Facade接口打交道便可。

. 適配器模式

定義

做爲一個基金交易平臺,須要提供一套接口規範,供各個基金公司接入。然而,各個基金公司的接口各不相同,沒有辦法直接和平臺接口對接。此時,各個基金公司須要自行實現一個適配器,適配器完成不一樣接口的轉換工做,使得基金公司的接口和平臺提供的接口對接上。

. 三種適配器

適配器模式有三種實現方式,下面都以基金交易平臺的例子來解釋。

  • 基金公司的交易接口:

/**
* 基金公司的交易接口
*/

class FundCompanyTrade{
   /**
    * 買入函數
    */

   public void mairu() {
       // ……
   }
   /**
    * 賣出函數
    */

   public void maichu() {
       // ……
   }
}
  • 基金交易平臺的交易接口

/**
* 基金交易平臺的交易接口
*/

interface FundPlatformTrade {
   // 買入接口
   void buy();
   // 賣出接口
   void sell();
}
  • 基金交易平臺均經過以下代碼調用各個基金公司的交易接口:

class Client {
   @Autowired
   private FundPlatformTrade fundPlatformTrade;
   /**
    * 買入基金
    */

    public void buy() {
       fundPlatformTrade.buy();
    }
   /**
    * 賣出基金
    */

    public void sell() {
       fundPlatformTrade.sell();
    }
}

方式1:類適配器

經過繼承來實現接口的轉換。

  • 基金交易適配器:

class Adapter extends FundCompanyTrade implements FundPlatformTrade {
   void buy() {
       mairu();
   }
   void sell(){
       maichu();
   }
}

適配器Adapter繼承了FundCompanyTrade,所以擁有了FundCompanyTrade買入和賣出的能力;適配器Adapter又實現了FundPlatformTrade,所以須要實現其中的買入和賣出接口,這個過程便完成了基金公司交易接口向基金平臺交易接口的轉換。

使用時,只需將Adapter經過Spring注入給Client類的fundPlatformTrade成員變量便可:

<!-- 聲明Adapter對象 -->
<bean name="adapter" class="Adapter">
</bean>
<!-- 將adapter注入給Client -->
<bean class="Client">
   <property name="fundPlatformTrade" ref="adapter" />
</bean>

方式2:對象適配器

經過組合來實現接口的轉換。

  • 基金交易適配器:

class Adapter implements FundPlatformTrade {
   @Autowired
   private FundCompanyTrade fundCompanyTrade;
   void buy() {
       fundCompanyTrade.mairu();
   }
   void sell(){
       fundCompanyTrade.maichu();
   }
}

這種方式中,適配器Adapter並未繼承FundCompanyTrade,而是將該對象做爲成員變量注入進來,同樣能夠達到一樣的效果。

方式3:接口適配器

當存在這樣一個接口,其中定義了N多的方法,而咱們如今卻只想使用其中的一個到幾個方法,若是咱們直接實現接口,那麼咱們要對全部的方法進行實現,哪怕咱們僅僅是對不須要的方法進行置空(只寫一對大括號,不作具體方法實現)也會致使這個類變得臃腫,調用也不方便,這時咱們可使用一個抽象類做爲中間件,即適配器,用這個抽象類實現接口,而在抽象類中全部的方法都進行置空,那麼咱們在建立抽象類的繼承類,並且重寫咱們須要使用的那幾個方法便可。

  • 目標接口:A

public interface A {
   void a();
   void b();
   void c();
   void d();
   void e();
   void f();
}
  • 適配器:Adapter
    實現全部函數,將全部函數先置空。

public abstract class Adapter implements A {
   public void a(){
       throw new UnsupportedOperationException("對象不支持這個功能");
   }
   public void b(){
       throw new UnsupportedOperationException("對象不支持這個功能");
   }
   public void c(){
       throw new UnsupportedOperationException("對象不支持這個功能");
   }
   public void d(){
       throw new UnsupportedOperationException("對象不支持這個功能");
   }
   public void e(){
       throw new UnsupportedOperationException("對象不支持這個功能");
   }
   public void f(){
       throw new UnsupportedOperationException("對象不支持這個功能");
   }
}
  • 實現類:Ashili
    繼承適配器類,選擇性地重寫相應函數。

public class Ashili extends Adapter {
   public void a(){
       System.out.println("實現A方法被調用");
   }
   public void d(){
       System.out.println("實現d方法被調用");
   }
}

. 迭代器模式

定義

迭代器模式用於在無需瞭解容器內部細節的狀況下,實現容器的迭代。

容器用於存儲數據,而容器的存儲結構種類繁多。在不使用適配器模式的狀況下,若是要迭代容器中的元素,就須要充分理解容器的存儲結構。存儲結構不一樣,致使了不一樣容器的迭代方式都不同。這無疑增長了咱們使用容器的成本。

而迭代器模式提出了一種迭代容器元素的新思路,迭代器規定了一組迭代容器的接口,做爲容器使用者,只需會用這套迭代器便可。容器自己須要實現這套迭代器接口,並實現其中的迭代函數。也就是容器提供方在提供容器的同時,還須要提供迭代器的實現。由於容器自己是瞭解本身的存儲結構的,由它來實現迭代函數很是合適。而咱們做爲容器的使用者,只需知道怎麼用迭代器便可,無需瞭解容器內部的存儲結構。

類圖描述

好好學java好好學java
在迭代器模式中,一共有兩種角色:迭代器 和 容器
  • 迭代器 Iterator:封裝了迭代容器的接口

  • 容器 Container:存儲元素的東西

    • 容器若要具有迭代的能力,就必須擁有getIterator()函數,該函數將會返回一個迭代器對象

    • 每一個容器都有屬於本身的迭代器內部類,該內部類實現了Iterator接口,並實現了其中用於迭代的兩個函數hasNext()和next()

    • boolean hasNext():用於判斷當前容器是否還有還沒有迭代完的元素

    • Object next():用於獲取下一個元素

代碼實現

  • 迭代器接口:

public interface Iterator {
  public boolean hasNext();
  public Object next();
}
  • 容器接口:

public interface Iterator {
  public boolean hasNext();
  public Object next();
}
  • 具體的容器(必須實現Container接口):

public class NameRepository implements Container {
  public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};
  @Override
  public Iterator getIterator() {
     return new NameIterator();
  }
  private class NameIterator implements Iterator {
     int index;
     @Override
     public boolean hasNext() {
        if(index < names.length){
           return true;
        }
        return false;
     }
     @Override
     public Object next() {
        if(this.hasNext()){
           return names[index++];
        }
        return null;
     }        
  }
}
  • 具體的容器實現了Container接口,並實現了其中的getIterator()函數,該函數用於返回該容器的迭代器對象。

  • 容器內部須要實現本身的迭代器內部類,該內部類實現Iterator接口,並實現了其中的hasNext()和next()函數。

當容器和容器的迭代器建立完畢後,接下來就輪到用戶使用了,使用就很是簡單了:

public class IteratorPatternDemo {
  public static void main(String[] args) {
     NameRepository namesRepository = new NameRepository();
     for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
        String name = (String)iter.next();
        System.out.println("Name : " + name);
     }    
  }
}
  • 對於使用者而言,只要知道Iterator接口,就可以迭代全部不一樣種類的容器了。

. 組合模式

定義

組合模式定義了樹形結構的物理存儲方式。

現實世界中樹形結構的東西,在代碼實現中,均可以用組合模式來表示。

好比:多級菜單、公司的組織結構等等。

下面就以多級菜單爲例,介紹組合模式。

例子

假設咱們要實現一個多級菜單,並實現多級菜單的增刪改查操做。菜單以下:

一級菜單A
   二級菜單A_1
       三級菜單A_1_1
       三級菜單A_1_2
       三級菜單A_1_3
   二級菜單A_2
一級菜單B
   二級菜單B_1
   二級菜單B_2
   二級菜單B_3
   二級菜單B_4
       三級菜單B_4_1
       三級菜單B_4_2
       三級菜單B_4_3
一級菜單C
   二級菜單C_1
   二級菜單C_2
   二級菜單C_3

菜單的特色以下:

  • 深度不限,能夠有無限級菜單

  • 每層菜單數量不限

類圖描述

  • Item表示樹中的節點;

  • Item中包含兩個成員變量:

    • parent:指向當前節點的父節點

    • childList:當前節點的子節點列表

  • 這種Item中又包含Item的關係就構成了組合模式。

注意:循環引用

在構建樹的過程當中,可能會出現循環引用,從而在遍歷樹的時候可能就會出現死循環。所以,咱們須要在添加節點的時候避免循環引用的出現。

咱們能夠在Item中再添加一個List成員變量,用於記錄根節點到當前節點的路徑。該路徑能夠用每一個節點的ID表示。一旦新加入的節點ID已經出如今當前路徑中的時候,就說明出現了循環引用,此時應該給出提示。

. 狀態模式

使用場景

若是一個函數中出現大量的、複雜的if-else判斷,這時候就要考慮使用狀態模式了。

由於大量的if-else中每每包含了大量的業務邏輯,頗有可能會隨着業務的發展而變化。若是將這些業務邏輯都寫死在一個類中,那麼當業務邏輯發生變化的時候就須要修改這個類,從而違反了開放封閉原則。而狀態模式就能很好地解決這一問題。

狀態模式將每個判斷分支都封裝成一個獨立的類,每個判斷分支成爲一種「狀態」,所以每個獨立的類就成爲一個「狀態類」。而且由一個全局狀態管理者Context來維護當前的狀態。

類圖描述

  • 在狀態模式中,每個判斷分支被成爲一種狀態,每一種狀態,都會被封裝成一個單獨的狀態類;

  • 全部的狀態類都有一個共同的接口——State

  • State接口中有一個doAction函數,每一個狀態類的狀態處理邏輯均在該函數中完成;必須將Context對象做爲doAction函數的參數傳入。該函數的結構以下:

class StateA implements State{
   public void doAction(Context context){
       if (知足條件) {
           // 執行相應的業務邏輯
       }
       else {
           // 設置下一跳狀態
           context.setState(new StateB());
           // 執行下一跳狀態
           context.doCurState();
       }
   }
}
  • 每一個狀態類的doAction函數中都有且僅有一對if-else,if中填寫知足條件時的業務邏輯,而else中填寫不知足條件時的業務邏輯。

  • else中的代碼都同樣,有且僅有這兩步:
    首先將context的state設爲下一個狀態對象;
    而後調用context的doCurState()執行;

  • Context類其實就是本來包含那個巨大、複雜的if-else的類。該類中持有了State對象,表示當前要執行的狀態對象。

  • Context類必需要有一個doCurState函數,該函數的代碼都同樣:state.doAction()

  • 開啓狀態判斷過程的代碼以下:

// 準備好第一個狀態
StateA stateA = new StateA();
// 設置第一個狀態
context.setState(stateA);
// 開始執行
context.doCurState();

優勢

狀態模式將本來在一個類中的龐大的if-else拆分紅一個個獨立的狀態類。本來這個包含龐大if-else的類成爲Context,包含了當前的狀態。Context只須要知道起始狀態類便可,不須要知道其餘狀態類的存在。也就是Context只與第一個狀態類發生耦合。而每個狀態類只和下一個狀態類發生耦合,從而造成一條狀態判斷鏈。狀態類之間的耦合經過Spring XML文件配置。這樣,當判斷邏輯發生變化的時候,只須要新增狀態類,並經過修改XML的方式將新的狀態類插入到判斷邏輯中。從而知足了開放封閉原則。
代理模式

. 代理模式

好好學java好好學java

代理模式是在不改變目標類和使用者的前提下,擴展該類的功能。

代理模式中存在『目標對象』和『代理對象』,它們必須實現相同的接口。用戶直接使用代理對象,而代理對象會將用戶的請求交給目標對象處理。代理對象能夠對用戶的請求增長額外的處理。

Java動態代理的使用

  • 首先你得擁有一個目標對象,該對象必需要實現一個接口:

public interface Subject   
{  
 public void doSomething();  
}  
public class RealSubject implements Subject   
{  
 public void doSomething()  
 
{  
   System.out.println( "call doSomething()" );  
 }  
}  
  • 其次,爲目標對象增長額外的邏輯:

    • 自定義一個類,並實現InvocationHandler接口;

    • 實現invoke函數,並將須要增長的邏輯寫在該函數中;

public class ProxyHandler implements InvocationHandler   
{  
 private Object proxied;  
 public ProxyHandler( Object proxied )  
 
{  
   this.proxied = proxied;  
 }  
 public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable  
 
{  
   //在轉調具體目標對象以前,能夠執行一些功能處理
   //轉調具體目標對象的方法
   return method.invoke( proxied, args);  
   //在轉調具體目標對象以後,能夠執行一些功能處理
 }    
}
  • 建立代理對象,調用者直接使用該對象便可:

RealSubject real = new RealSubject();   
Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
    new Class[]{Subject.class},
    new ProxyHandler(real));
   proxySubject.doSomething();

轉自:http://blog.csdn.net/u010425776/article/details/79211117
文章有不當之處,歡迎指正,你也能夠關注個人微信公衆號:好好學java,獲取java等優質視頻教程、學習資源。

相關文章
相關標籤/搜索