1. 程式人生 > 其它 >柴毛毛大話設計模式——開發常用的設計模式梳理

柴毛毛大話設計模式——開發常用的設計模式梳理

寫在最前

  • 本文是筆者的一點經驗總結,主要介紹幾種在Web開發中使用頻率較高的設計模式。
  • 本文篇幅較長,建議各位同學挑選感興趣的設計模式閱讀。
  • 在閱讀的同時,也麻煩各位大佬多多分享!有你們的肯定,才有我繼續分享的動力
  • 如需轉載,請與我聯絡!

人工智慧看面相

  • 最近忙裡偷閒,對人工智慧看面相進行了一些優化,歡迎各位大佬體驗!
  • 體驗後懇請各位大佬分享朋友圈!

基礎學習:UML四種關係

耦合度大小關係

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

依賴(Dependency)

  • 一個人(Person)可以買車(car)和房子(House),那麼就可以稱:Person類**依賴於**Car類和House類
  • 這裡注意與下面的關聯關係區分:Person類裡並沒有使用Car和House型別的屬性,Car和House的例項是以參量的方式傳入到buy()方法中。
  • 依賴關係在Java語言中體現為局域變數方法的形參,或者對靜態方法的呼叫。

關聯(Association)

  • 它使一個類知道另一個類的屬性和方法
  • 關聯可以是雙向的,也可以是單向的。
  • 在Java語言中,關聯關係一般使用成員變數來實現。

聚合(Aggregation)

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

組合(Composition)

  • 組合是關聯關係的一種,是比聚合關係強的關係,也以成員變數的形式出現。
  • 在某一個時刻,部分物件只能和一個整體物件發生組合關係,由後者排他地負責生命週期。
  • 部分和整體的生命週期一樣。
  • 整體可以將部分傳遞給另一個物件,這時候該部分的生命週期由新整體控制,然後舊整體可以死亡。

策略模式

什麼是策略模式

一個類中的一些行為,可能會隨著系統的迭代而發生變化。為了使得該類滿足開放-封閉原則(即:具備可擴充套件性 或 彈性),我們需要將這些未來會發生動態變化的行為從該類中剝離出來,並通過預測未來業務發展的方式,為這些行為抽象出共有的特徵,封裝在抽象類或介面中,並通過它們的實現類提供具體的行為。原本類中需要持有該抽象類/介面的引用。在使用時,將某一個具體的實現類物件注入給該類所持有的介面/抽象類的引用。

類圖描述

如果類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個子類,保證任何一個父類的行為和特徵均是該繼承體系中共有的!

問:隨著需求的變化,父類中需要增加共有行為時怎麼辦?這就破壞了“開放封閉原則”。

這並未破壞“開放封閉原則”!在系統迭代更新的過程中,修改原有的程式碼是在所難免的,這並不違背“開放封閉原則”。 “開放封閉原則”要求我們:當系統在迭代過程中,第一次出現某一型別的需求時,是允許修改的;在此時,應該對系統進行修改,並進行合理地設計,以保證對該型別需求的再次修改具備可擴充套件性。當再一次出現該型別的需求時,就不應該修改原有程式碼,只允許通過擴充套件來滿足需求。


觀察者模式

觀察者模式是什麼

如果出現如下場景需求時,就需要使用觀察者模式。

如果存在一系列類,他們都需要向指定類獲取指定的資料,當獲取到資料後需要觸發相應的業務邏輯。這種場景就可以用觀察者模式來實現。

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

類圖描述

  • 被觀察者是資料提供方,觀察者是資料獲取方
  • 一個普通的類,如果要成為觀察者,獲取指定的資料,一共需要如下幾步:
    • 首先,需要實現Observer介面,並實現update函式;
    • 然後,在該函式中定義獲取資料後的業務邏輯;
    • update(Observable, Object)一共有兩個引數:
      • Observable:被觀察者物件(資料提供方)
      • Object:資料本身
    • 最後,通過呼叫 被觀察者 的addObservable()或者通過Spring的XML配置檔案完成觀察者向被觀察者的注入。此時,該觀察者物件就會被新增進 被觀察者 的List中。
  • 呼叫者才是真正的資料提供方。當呼叫者需要廣播最新資料時,只需呼叫 被觀察者 的notidyObservers()函式,該函式會遍歷List集合,並依次呼叫每個Observer的update函式,從而完成資料的傳送,並觸發每個Observer收到資料後的業務邏輯。

兩種註冊觀察者的方式

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

1. 執行前,通過Spring XML

在系統執行前,如果觀察者數量可以確定,並在執行過程中不會發生變化,那麼就可以在XML中完成List<Observer>物件的注入,這種方式程式碼將會比較簡潔。

  1. 配置好所有 觀察者 bean
<!-- 建立observerA -->
<bean name="observerA" class="ObservserA">
</bean>

<!-- 建立observerB-->
<bean name="observerB" class="ObservserB">
</bean>
  1. 配置好 被觀察者 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物件注入ObservableobserverList中。

@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介面。若要實現觀察者模式,直接使用這兩個工具包即可。


裝飾模式

何時使用

  1. 需要增強一個物件中某些函式的功能。
  2. 需要動態地給一個物件增加功能,這些功能可以再動態地撤銷。
  3. 需要增加 由一些基本功能排列組合 而產生的大量功能,從而使繼承體系大爆炸。

類圖描述

在裝飾模式中的各個角色有:

  • 抽象構件(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();

執行過程如下:

優點

  1. Decorator模式與繼承關係的目的都是要擴充套件物件的功能,但是Decorator可以提供比繼承更多的靈活性。繼承通過覆蓋的方式重寫需要擴充套件的函式,當然也可以通過super.xxx()獲取原本的功能,然後在該功能基礎上擴充套件新功能,但它只能增加某一項功能;如果要通過繼承實現增加多種功能,那麼需要多層繼承多個類來實現;而Decorator模式可以在原有功能的基礎上通過組合來增加新功能,這些新功能已經被封裝成一個個獨立的裝飾類,在執行期間通過搭積木的方式選擇裝飾類拼湊即可。
  2. 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。

缺點

  1. 這種比繼承更加靈活機動的特性,也同時意味著更加多的複雜性。
  2. 裝飾模式會導致設計中出現許多小類,如果過度使用,會使程式變得很複雜。
  3. 裝飾模式是針對抽象元件(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(){}      
}

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

  1. 需要額外的工作來實現序列化,否則每次反序列化一個序列化的物件時都會建立一個新的例項。
  2. 可以使用反射強行呼叫私有構造器(如果要避免這種情況,可以修改構造器,讓它在建立第二個例項的時候拋異常)。

而列舉類很好的解決了這兩個問題,使用列舉除了執行緒安全和防止反射呼叫構造器之外,還提供了自動序列化機制,防止反序列化的時候建立新的物件。因此,《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方法被呼叫");
    }
}

迭代器模式

定義

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

容器用於儲存資料,而容器的儲存結構種類繁多。在不使用介面卡模式的情況下,如果要迭代容器中的元素,就需要充分理解容器的儲存結構。儲存結構不同,導致了不同容器的迭代方式都不一樣。這無疑增加了我們使用容器的成本。

而迭代器模式提出了一種迭代容器元素的新思路,迭代器規定了一組迭代容器的介面,作為容器使用者,只需會用這套迭代器即可。容器本身需要實現這套迭代器介面,並實現其中的迭代函式。也就是容器提供方在提供容器的同時,還需要提供迭代器的實現。因為容器本身是瞭解自己的儲存結構的,由它來實現迭代函式非常合適。而我們作為容器的使用者,只需知道怎麼用迭代器即可,無需瞭解容器內部的儲存結構。

類圖描述

在迭代器模式中,一共有兩種角色:迭代器 和 容器

  • 迭代器 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動態代理的使用

  • 首先你得擁有一個目標物件,該物件必須要實現一個介面:
public interface Subject   
{   
  public void doSomething();   
}  
public class RealSubject implements Subject   
{   
  public void doSomething()   
  {   
    System.out.println( "call doSomething()" );   
  }   
}   
  • 其次,為目標物件增加額外的邏輯:
    1. 自定義一個類,並實現InvocationHandler介面;
    2. 實現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();