鍍金池/ 教程/ Java/ 常見設計模式
Struts2
Java 泛型
排序算法
Java 內存管理
Webservice
Spring
輸入輸出流
Socket
字符串與數(shù)組
面向對象編程
海量數(shù)據處理
Hibernate
Netty
基本類型與運算符
常見設計模式
Java 虛擬機
Java 多線程
JDBC
搭建 Java 開發(fā)環(huán)境
Java 數(shù)據庫操作
異常處理
集合類
Servlet 與 JSP

常見設計模式

Java 開發(fā)中的23種設計模式詳解(轉)

設計模式(Design Patterns) ——可復用面向對象軟件的基礎

設計模式(Design pattern)是一套被反復使用、多數(shù)人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式于己于他人于系統(tǒng)都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現(xiàn)在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重復發(fā)生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。本章系 Java 之美[從菜鳥到高手演變]系列之設計模式,我們會以理論與實踐相結合的方式來進行本章的學習,希望廣大程序愛好者,學好設計模式,做一個優(yōu)秀的軟件工程師!

企業(yè)級項目實戰(zhàn)(帶源碼)地址: http://zz563143188.iteye.com/blog/1825168

23種模式 java 實現(xiàn)源碼下載地址: http://pan.baidu.com/share/link?shareid=372668&uk=4076915866#dir/path=%2F%E5%AD%A6%E4%B9%A0%E6%96%87%E4%BB%B6

一、設計模式的分類

總體來說設計模式分為三大類:

創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。

其實還有兩類:并發(fā)型模式和線程池模式。用一個圖片來整體描述一下:

http://wiki.jikexueyuan.com/project/java-special-topic/images/3.jpg" alt="" />

二、設計模式的六大原則

1、開閉原則(Open Close Principle)

開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現(xiàn)一個熱插拔的效果。所以一句話概括就是:為了使程序的擴展性好,易于維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,后面的具體設計中我們會提到這點。

2、里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。 LSP 是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現(xiàn)“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現(xiàn),所以里氏代換原則是對實現(xiàn)抽象化的具體步驟的規(guī)范。—— From Baidu 百科

3、依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,具體內容:真對接口編程,依賴于抽象而不依賴于具體。

4、接口隔離原則(Interface Segregation Principle)

這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發(fā),為了升級和維護方便。所以上文中多次出現(xiàn):降低依賴,降低耦合。

5、迪米特法則(最少知道原則)(Demeter Principle)

為什么叫最少知道原則,就是說:一個實體應當盡量少的與其他實體之間發(fā)生相互作用,使得系統(tǒng)功能模塊相對獨立。

6、合成復用原則(Composite Reuse Principle)

原則是盡量使用合成/聚合的方式,而不是使用繼承。

三、Java 的23中設計模式

從這一塊開始,我們詳細介紹 Java 中23種設計模式的概念,應用場景等情況,并結合他們的特點及設計模式的原則進行分析。

1、工廠方法模式(Factory Method)

工廠方法模式分為三種:

11、普通工廠模式,就是建立一個工廠類,對實現(xiàn)了同一接口的一些類進行實例的創(chuàng)建。首先看下關系圖:

http://wiki.jikexueyuan.com/project/java-special-topic/images/14.png" alt="" />

舉例如下:(我們舉一個發(fā)送郵件和短信的例子)

首先,創(chuàng)建二者的共同接口:

[java] view plaincopy
public interface Sender {  
    public void Send();  
} 

其次,創(chuàng)建實現(xiàn)類:

[java] view plaincopy
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  
[java] view plaincopy
public class SmsSender implements Sender {  

    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
} 

最后,建工廠類:

[java] view plaincopy
public class SendFactory {  

    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("請輸入正確的類型!");  
            return null;  
        }  
    }  
}  

我們來測試下:

public class FactoryTest {  

    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }  
}

輸出:this is sms sender!

22、多個工廠方法模式,是對普通工廠方法模式的改進,在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創(chuàng)建對象,而多個工廠方法模式是提供多個工廠方法,分別創(chuàng)建對象。關系圖:

http://wiki.jikexueyuan.com/project/java-special-topic/images/4.jpg" alt="" />

將上面的代碼做下修改,改動下 SendFactory 類就行,如下:

[java] view plaincopypublic class SendFactory {  
   public Sender produceMail(){  
        return new MailSender();  
    }  

    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  

測試類如下:

[java] view plaincopy
public class FactoryTest {  

    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}  

輸出:this is mailsender!

33、靜態(tài)工廠方法模式,將上面的多個工廠方法模式里的方法置為靜態(tài)的,不需要創(chuàng)建實例,直接調用即可。

[java] view plaincopy
public class SendFactory {  

    public static Sender produceMail(){  
        return new MailSender();  
    }  

    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}  
[java] view plaincopy
public class FactoryTest {  

    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
} 

輸出:this is mailsender!

總體來說,工廠模式適合:凡是出現(xiàn)了大量的產品需要創(chuàng)建,并且具有共同的接口時,可以通過工廠方法模式進行創(chuàng)建。在以上的三種模式中,第一種如果傳入的字符串有誤,不能正確創(chuàng)建對象,第三種相對于第二種,不需要實例化工廠類,所以,大多數(shù)情況下,我們會選用第三種——靜態(tài)工廠方法模式。

2、抽象工廠模式(Abstract Factory)

工廠方法模式有一個問題就是,類的創(chuàng)建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了閉包原則,所以,從設計角度考慮,有一定的問題,如何解決?就用到抽象工廠模式,創(chuàng)建多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。因為抽象工廠不太好理解,我們先看看圖,然后就和代碼,就比較容易理解。

http://wiki.jikexueyuan.com/project/java-special-topic/images/5.jpg" alt="" />

請看例子:

[java] view plaincopy
public interface Sender {  
    public void Send();  
}  
兩個實現(xiàn)類:

[java] view plaincopy
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  
[java] view plaincopy
public class SmsSender implements Sender {  

    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
} 

兩個工廠類:

[java] view plaincopy
public class SendMailFactory implements Provider {  

    @Override  
    public Sender produce(){  
        return new MailSender();  
    }  
}  
[java] view plaincopy
public class SendSmsFactory implements Provider{  

    @Override  
    public Sender produce() {  
        return new SmsSender();  
    }  
} 

在提供一個接口:

[java] view plaincopy
public interface Provider {  
    public Sender produce();  
}  

測試類:

[java] view plaincopy
public class Test {  

    public static void main(String[] args) {  
        Provider provider = new SendMailFactory();  
        Sender sender = provider.produce();  
        sender.Send();  
    }  
}

其實這個模式的好處就是,如果你現(xiàn)在想增加一個功能:發(fā)及時信息,則只需做一個實現(xiàn)類,實現(xiàn) Sender 接口,同時做一個工廠類,實現(xiàn) Provider 接口,就 OK 了,無需去改動現(xiàn)成的代碼。這樣做,拓展性較好!

3、單例模式(Singleton)

單例對象(Singleton)是一種常用的設計模式。在 Java 應用中,單例對象能保證在一個 JVM 中,該對象只有一個實例存在。這樣的模式有幾個好處:

1、某些類創(chuàng)建比較頻繁,對于一些大型的對象,這是一筆很大的系統(tǒng)開銷。

2、省去了 new 操作符,降低了系統(tǒng)內存的使用頻率,減輕 GC 壓力。

3、有些類如交易所的核心交易引擎,控制著交易流程,如果該類可以創(chuàng)建多個的話,系統(tǒng)完全亂了。(比如一個軍隊出現(xiàn)了多個司令員同時指揮,肯定會亂成一團),所以只有使用單例模式,才能保證核心交易服務器獨立控制整個流程。

首先我們寫一個簡單的單例類:

[java] view plaincopy
public class Singleton {  

    /* 持有私有靜態(tài)實例,防止被引用,此處賦值為null,目的是實現(xiàn)延遲加載 */  
    private static Singleton instance = null;  

    /* 私有構造方法,防止被實例化 */  
    private Singleton() {  
    }  

    /* 靜態(tài)工程方法,創(chuàng)建實例 */  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  

    /* 如果該對象被用于序列化,可以保證對象在序列化前后保持一致 */  
    public Object readResolve() {  
        return instance;  
    }  
}  

這個類可以滿足基本要求,但是,像這樣毫無線程安全保護的類,如果我們把它放入多線程的環(huán)境下,肯定就會出現(xiàn)問題了,如何解決?我們首先會想到對 getInstance 方法加 synchronized 關鍵字,如下:

[java] view plaincopy
public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    } 

但是,synchronized 關鍵字鎖住的是這個對象,這樣的用法,在性能上會有所下降,因為每次調用 getInstance(),都要對對象上鎖,事實上,只有在第一次創(chuàng)建對象的時候需要加鎖,之后就不需要了,所以,這個地方需要改進。我們改成下面這個:

[java] view plaincopy
public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (instance) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  

似乎解決了之前提到的問題,將 synchronized 關鍵字加在了內部,也就是說當調用的時候是不需要加鎖的,只有在 instance 為 null,并創(chuàng)建對象的時候才需要加鎖,性能有一定的提升。但是,這樣的情況,還是有可能有問題的,看下面的情況:在 Java 指令中創(chuàng)建對象和賦值操作是分開進行的,也就是說 instance = new Singleton();語句是分兩步執(zhí)行的。但是 JVM 并不保證這兩個操作的先后順序,也就是說有可能 JVM 會為新的 Singleton 實例分配空間,然后直接賦值給 instance 成員,然后再去初始化這個 Singleton 實例。這樣就可能出錯了,我們以 A、B 兩個線程為例:

a>A、B 線程同時進入了第一個 if 判斷

b>A 首先進入 synchronized 塊,由于 instance 為 null,所以它執(zhí)行 instance = new Singleton();

c> 由于 JVM 內部的優(yōu)化機制,JVM 先畫出了一些分配給 Singleton 實例的空白內存,并賦值給 instance 成員(注意此時 JVM 沒有開始初始化這個實例),然后 A 離開了 synchronized 塊。

d>B 進入 synchronized 塊,由于 instance 此時不是 null,因此它馬上離開了synchronized 塊并將結果返回給調用該方法的程序。

e> 此時 B 線程打算使用 Singleton 實例,卻發(fā)現(xiàn)它沒有被初始化,于是錯誤發(fā)生了。

所以程序還是有可能發(fā)生錯誤,其實程序在運行過程是很復雜的,從這點我們就可以看出,尤其是在寫多線程環(huán)境下的程序更有難度,有挑戰(zhàn)性。我們對該程序做進一步優(yōu)化:

[java] view plaincopy
private static class SingletonFactory{           
        private static Singleton instance = new Singleton();           
    }           
    public static Singleton getInstance(){           
        return SingletonFactory.instance;           
    }

實際情況是,單例模式使用內部類來維護單例的實現(xiàn),JVM 內部的機制能夠保證當一個類被加載的時候,這個類的加載過程是線程互斥的。這樣當我們第一次調用 getInstance 的時候,JVM 能夠幫我們保證 instance 只被創(chuàng)建一次,并且會保證把賦值給 instance 的內存初始化完畢,這樣我們就不用擔心上面的問題。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。這樣我們暫時總結一個完美的單例模式:

[java] view plaincopy
public class Singleton {  

    /* 私有構造方法,防止被實例化 */  
    private Singleton() {  
    }  

    /* 此處使用一個內部類來維護單例 */  
    private static class SingletonFactory {  
        private static Singleton instance = new Singleton();  
    }  

    /* 獲取實例 */  
    public static Singleton getInstance() {  
        return SingletonFactory.instance;  
    }  

    /* 如果該對象被用于序列化,可以保證對象在序列化前后保持一致 */  
    public Object readResolve() {  
        return getInstance();  
    }  
}  

其實說它完美,也不一定,如果在構造函數(shù)中拋出異常,實例將永遠得不到創(chuàng)建,也會出錯。所以說,十分完美的東西是沒有的,我們只能根據實際情況,選擇最適合自己應用場景的實現(xiàn)方法。也有人這樣實現(xiàn):因為我們只需要在創(chuàng)建類的時候進行同步,所以只要將創(chuàng)建和 getInstance()分開,單獨為創(chuàng)建加 synchronized 關鍵字,也是可以的:

[java] view plaincopy
public class SingletonTest {  

    private static SingletonTest instance = null;  

    private SingletonTest() {  
    }  

    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  

    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  
} 

考慮性能的話,整個程序只需創(chuàng)建一次實例,所以性能也不會有什么影響。

補充:采用"影子實例"的辦法為單例對象的屬性同步更新

[java] view plaincopy
public class SingletonTest {  

    private static SingletonTest instance = null;  
    private Vector properties = null;  

    public Vector getProperties() {  
        return properties;  
    }  

    private SingletonTest() {  
    }  

    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  

    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  

    public void updateProperties() {  
        SingletonTest shadow = new SingletonTest();  
        properties = shadow.getProperties();  
    }  
}

通過單例模式的學習告訴我們:

1、單例模式理解起來簡單,但是具體實現(xiàn)起來還是有一定的難度。

2、synchronized 關鍵字鎖定的是對象,在用的時候,一定要在恰當?shù)牡胤绞褂茫ㄗ⒁庑枰褂面i的對象和過程,可能有的時候并不是整個對象及整個過程都需要鎖)。

到這兒,單例模式基本已經講完了,結尾處,筆者突然想到另一個問題,就是采用類的靜態(tài)方法,實現(xiàn)單例模式的效果,也是可行的,此處二者有什么不同?

首先,靜態(tài)類不能實現(xiàn)接口。(從類的角度說是可以的,但是那樣就破壞了靜態(tài)了。因為接口中不允許有 static 修飾的方法,所以即使實現(xiàn)了也是非靜態(tài)的)

其次,單例可以被延遲初始化,靜態(tài)類一般在第一次加載是初始化。之所以延遲加載,是因為有些類比較龐大,所以延遲加載有助于提升性能。

再次,單例類可以被繼承,他的方法可以被覆寫。但是靜態(tài)類內部方法都是 static,無法被覆寫。

最后一點,單例類比較靈活,畢竟從實現(xiàn)上只是一個普通的 Java 類,只要滿足單例的基本需求,你可以在里面隨心所欲的實現(xiàn)一些其它功能,但是靜態(tài)類不行。從上面這些概括中,基本可以看出二者的區(qū)別,但是,從另一方面講,我們上面最后實現(xiàn)的那個單例模式,內部就是用一個靜態(tài)類來實現(xiàn)的,所以,二者有很大的關聯(lián),只是我們考慮問題的層面不同罷了。兩種思想的結合,才能造就出完美的解決方案,就像 HashMap 采用數(shù)組+鏈表來實現(xiàn)一樣,其實生活中很多事情都是這樣,單用不同的方法來處理問題,總是有優(yōu)點也有缺點,最完美的方法是,結合各個方法的優(yōu)點,才能最好的解決問題!

4、建造者模式(Builder)

工廠類模式提供的是創(chuàng)建單個類的模式,而建造者模式則是將各種產品集中起來進行管理,用來創(chuàng)建復合對象,所謂復合對象就是指某個類具有不同的屬性,其實建造者模式就是前面抽象工廠模式和最后的 Test 結合起來得到的。我們看一下代碼:

還和前面一樣,一個 Sender 接口,兩個實現(xiàn)類 MailSender 和 SmsSender。最后,建造者類如下:

[java] view plaincopy
public class Builder {  

    private List<Sender> list = new ArrayList<Sender>();  

    public void produceMailSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new MailSender());  
        }  
    }  

    public void produceSmsSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new SmsSender());  
        }  
    }  
} 

測試類:

[java] view plaincopy
public class Test {  

    public static void main(String[] args) {  
        Builder builder = new Builder();  
        builder.produceMailSender(10);  
    }  
}  

從這點看出,建造者模式將很多功能集成到一個類里,這個類可以創(chuàng)造出比較復雜的東西。所以與工程模式的區(qū)別就是:工廠模式關注的是創(chuàng)建單個產品,而建造者模式則關注創(chuàng)建符合對象,多個部分。因此,是選擇工廠模式還是建造者模式,依實際情況而定。

5、原型模式(Prototype)

原型模式雖然是創(chuàng)建型的模式,但是與工程模式沒有關系,從名字即可看出,該模式的思想就是將一個對象作為原型,對其進行復制、克隆,產生一個和原對象類似的新對象。本小結會通過對象的復制,進行講解。在 Java 中,復制對象是通過 clone()實現(xiàn)的,先創(chuàng)建一個原型類:

[java] view plaincopy
public class Prototype implements Cloneable {  

    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
}  

很簡單,一個原型類,只需要實現(xiàn) Cloneable 接口,覆寫 clone 方法,此處 clone 方法可以改成任意的名稱,因為 Cloneable 接口是個空接口,你可以任意定義實現(xiàn)類的方法名,如cloneA 或者 cloneB,因為此處的重點是 super.clone()這句話,super.clone()調用的是Object 的 clone()方法,而在 Object 類中,clone()是 native 的,具體怎么實現(xiàn),我會在另一篇文章中,關于解讀 Java 中本地方法的調用,此處不再深究。在這兒,我將結合對象的淺復制和深復制來說一下,首先需要了解對象深、淺復制的概念:

淺復制:將一個對象復制后,基本數(shù)據類型的變量都會重新創(chuàng)建,而引用類型,指向的還是原對象所指向的。

深復制:將一個對象復制后,不論是基本數(shù)據類型還有引用類型,都是重新創(chuàng)建的。簡單來說,就是深復制進行了完全徹底的復制,而淺復制不徹底。

此處,寫一個深淺復制的例子:

[java] view plaincopy
public class Prototype implements Cloneable, Serializable {  

    private static final long serialVersionUID = 1L;  
    private String string;  

    private SerializableObject obj;  

    /* 淺復制 */  
    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  

    /* 深復制 */  
    public Object deepClone() throws IOException, ClassNotFoundException {  

        /* 寫入當前對象的二進制流 */  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);  

        /* 讀出二進制流產生的新對象 */  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        return ois.readObject();  
    }  

    public String getString() {  
        return string;  
    }  

    public void setString(String string) {  
        this.string = string;  
    }  

    public SerializableObject getObj() {  
        return obj;  
    }  

    public void setObj(SerializableObject obj) {  
        this.obj = obj;  
    }  

}  

class SerializableObject implements Serializable {  
    private static final long serialVersionUID = 1L;  
}  

要實現(xiàn)深復制,需要采用流的形式讀入當前對象的二進制輸入,再寫出二進制數(shù)據對應的對象。 我們接著討論設計模式,上篇文章我講完了5種創(chuàng)建型模式,這章開始,我將講下7種結構型模式:適配器模式、裝飾模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。其中對象的適配器模式是各種模式的起源,我們看下面的圖:

http://wiki.jikexueyuan.com/project/java-special-topic/images/6.jpg" alt="" />

適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示,目的是消除由于接口不匹配所造成的類的兼容性問題。主要分為三類:類的適配器模式、對象的適配器模式、接口的適配器模式。首先,我們來看看類的適配器模式,先看類圖:

http://wiki.jikexueyuan.com/project/java-special-topic/images/7.jpg" alt="" />

核心思想就是:有一個 Source 類,擁有一個方法,待適配,目標接口時 Targetable,通過Adapter 類,將 Source 的功能擴展到 Targetable 里,看代碼:

[java] view plaincopy
public class Source {  

    public void method1() {  
        System.out.println("this is original method!");  
    }  
}  
[java] view plaincopy
public interface Targetable {  

    /* 與原類中的方法相同 */  
    public void method1();  

    /* 新類的方法 */  
    public void method2();  
}  
[java] view plaincopy
public class Adapter extends Source implements Targetable {  

    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  
}

Adapter 類繼承 Source 類,實現(xiàn) Targetable 接口,下面是測試類:

[java] view plaincopy
public class AdapterTest {  

    public static void main(String[] args) {  
        Targetable target = new Adapter();  
        target.method1();  
        target.method2();  
    }  
}  

輸出:

this is original method!
this is the targetable method!

這樣 Targetable 接口的實現(xiàn)類就具有了 Source 類的功能。

對象的適配器模式

基本思路和類的適配器模式相同,只是將 Adapter 類作修改,這次不繼承 Source 類,而是持有 Source 類的實例,以達到解決兼容性的問題??磮D:

http://wiki.jikexueyuan.com/project/java-special-topic/images/8.jpg" alt="" />

只需要修改 Adapter 類的源碼即可:

[java] view plaincopy
public class Wrapper implements Targetable {  

    private Source source;  

    public Wrapper(Source source){  
        super();  
        this.source = source;  
    }  
    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  

    @Override  
    public void method1() {  
        source.method1();  
    }  
}

測試類:

[java] view plaincopy
public class AdapterTest {  

    public static void main(String[] args) {  
        Source source = new Source();  
        Targetable target = new Wrapper(source);  
        target.method1();  
        target.method2();  
    }  
} 

輸出與第一種一樣,只是適配的方法不同而已。

第三種適配器模式是接口的適配器模式,接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當我們寫該接口的實現(xiàn)類時,必須實現(xiàn)該接口的所有方法,這明顯有時比較浪費,因為并不是所有的方法都是我們需要的,有時只需要某一些,此處為了解決這個問題,我們引入了接口的適配器模式,借助于一個抽象類,該抽象類實現(xiàn)了該接口,實現(xiàn)了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯(lián)系,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行??匆幌骂悎D:

http://wiki.jikexueyuan.com/project/java-special-topic/images/9.jpg" alt="" />

這個很好理解,在實際開發(fā)中,我們也常會遇到這種接口中定義了太多的方法,以致于有時我們在一些實現(xiàn)類中并不是都需要??创a:

[java] view plaincopy
public interface Sourceable {  

    public void method1();  
    public void method2();  
} 

抽象類 Wrapper2:

[java] view plaincopy
public abstract class Wrapper2 implements Sourceable{  

    public void method1(){}  
    public void method2(){}  
}  
[java] view plaincopy
public class SourceSub1 extends Wrapper2 {  
    public void method1(){  
        System.out.println("the sourceable interface's first Sub1!");  
    }  
}  
[java] view plaincopy
public class SourceSub2 extends Wrapper2 {  
    public void method2(){  
        System.out.println("the sourceable interface's second Sub2!");  
    }  
}  
[java] view plaincopy
public class WrapperTest {  

    public static void main(String[] args) {  
        Sourceable source1 = new SourceSub1();  
        Sourceable source2 = new SourceSub2();  

        source1.method1();  
        source1.method2();  
        source2.method1();  
        source2.method2();  
    }  
} 

測試輸出:

the sourceable interface's first Sub1!
the sourceable interface's second Sub2!

達到了我們的效果!

講了這么多,總結一下三種適配器模式的應用場景:

類的適配器模式:當希望將一個類轉換成滿足另一個新接口的類時,可以使用類的適配器模式,創(chuàng)建一個新類,繼承原有的類,實現(xiàn)新的接口即可。

對象的適配器模式:當希望將一個對象轉換成滿足另一個新接口的對象時,可以創(chuàng)建一個 Wrapper 類,持有原類的一個實例,在 Wrapper 類的方法中,調用實例的方法就行。

接口的適配器模式:當不希望實現(xiàn)一個接口中所有的方法時,可以創(chuàng)建一個抽象類 Wrapper,實現(xiàn)所有方法,我們寫別的類的時候,繼承抽象類即可。

7、裝飾模式(Decorator)

顧名思義,裝飾模式就是給一個對象增加一些新的功能,而且是動態(tài)的,要求裝飾對象和被裝飾對象實現(xiàn)同一個接口,裝飾對象持有被裝飾對象的實例,關系圖如下:

http://wiki.jikexueyuan.com/project/java-special-topic/images/10.jpg" alt="" />

Source 類是被裝飾類,Decorator 類是一個裝飾類,可以為 Source 類動態(tài)的添加一些功能,代碼如下:

[java] view plaincopy
public interface Sourceable {  
    public void method();  
}  
[java] view plaincopy
public class Source implements Sourceable {  

    @Override  
    public void method() {  
        System.out.println("the original method!");  
    }  
}  
[java] view plaincopy
public class Decorator implements Sourceable {  

    private Sourceable source;  

    public Decorator(Sourceable source){  
        super();  
        this.source = source;  
    }  
    @Override  
    public void method() {  
        System.out.println("before decorator!");  
        source.method();  
        System.out.println("after decorator!");  
    }  
} 

測試類:

[java] view plaincopy
public class DecoratorTest {  

    public static void main(String[] args) {  
        Sourceable source = new Source();  
        Sourceable obj = new Decorator(source);  
        obj.method();  
    }  
}

輸出:

before decorator!
the original method!
after decorator!

裝飾器模式的應用場景:

1、需要擴展一個類的功能。

2、動態(tài)的為一個對象增加功能,而且還能動態(tài)撤銷。(繼承不能做到這一點,繼承的功能是靜態(tài)的,不能動態(tài)增刪。)

缺點:產生過多相似的對象,不易排錯!

8、代理模式(Proxy)

其實每個模式名稱就表明了該模式的作用,代理模式就是多一個代理類出來,替原對象進行一些操作,比如我們在租房子的時候回去找中介,為什么呢?因為你對該地區(qū)房屋的信息掌握的不夠全面,希望找一個更熟悉的人去幫你做,此處的代理就是這個意思。再如我們有的時候打官司,我們需要請律師,因為律師在法律方面有專長,可以替我們進行操作,表達我們的想法。先來看看關系圖:

http://wiki.jikexueyuan.com/project/java-special-topic/images/11.jpg" alt="" />

根據上文的闡述,代理模式就比較容易的理解了,我們看下代碼:

[java] view plaincopy
public interface Sourceable {  
    public void method();  
}  
[java] view plaincopy
public class Source implements Sourceable {  

    @Override  
    public void method() {  
        System.out.println("the original method!");  
    }  
}  
[java] view plaincopy
public class Proxy implements Sourceable {  

    private Source source;  
    public Proxy(){  
        super();  
        this.source = new Source();  
    }  
    @Override  
    public void method() {  
        before();  
        source.method();  
        atfer();  
    }  
    private void atfer() {  
        System.out.println("after proxy!");  
    }  
    private void before() {  
        System.out.println("before proxy!");  
    }  
} 

測試類:

[java] view plaincopy
public class ProxyTest {  

    public static void main(String[] args) {  
        Sourceable source = new Proxy();  
        source.method();  
    }  

}

輸出:

before proxy!
the original method!
after proxy!

代理模式的應用場景:

如果已有的方法在使用的時候需要對原有的方法進行改進,此時有兩種辦法:

1、修改原有的方法來適應。這樣違反了“對擴展開放,對修改關閉”的原則。

2、就是采用一個代理類調用原有的方法,且對產生的結果進行控制。這種方法就是代理模式。

使用代理模式,可以將功能劃分的更加清晰,有助于后期維護!

9、外觀模式(Facade)

外觀模式是為了解決類與類之家的依賴關系的,像 spring 一樣,可以將類和類之間的關系配置到配置文件中,而外觀模式就是將他們的關系放在一個 Facade 類中,降低了類類之間的耦合度,該模式中沒有涉及到接口,看下類圖:(我們以一個計算機的啟動過程為例)

http://wiki.jikexueyuan.com/project/java-special-topic/images/12.jpg" alt="" />

我們先看下實現(xiàn)類:

[java] view plaincopy
public class CPU {  

    public void startup(){  
        System.out.println("cpu startup!");  
    }  

    public void shutdown(){  
        System.out.println("cpu shutdown!");  
    }  
}  
[java] view plaincopy
public class Memory {  

    public void startup(){  
        System.out.println("memory startup!");  
    }  

    public void shutdown(){  
        System.out.println("memory shutdown!");  
    }  
}  
[java] view plaincopy
public class Disk {  

    public void startup(){  
        System.out.println("disk startup!");  
    }  

    public void shutdown(){  
        System.out.println("disk shutdown!");  
    }  
}  
[java] view plaincopy
public class Computer {  
    private CPU cpu;  
    private Memory memory;  
    private Disk disk;  

    public Computer(){  
        cpu = new CPU();  
        memory = new Memory();  
        disk = new Disk();  
    }  

    public void startup(){  
        System.out.println("start the computer!");  
        cpu.startup();  
        memory.startup();  
        disk.startup();  
        System.out.println("start computer finished!");  
    }  

    public void shutdown(){  
        System.out.println("begin to close the computer!");  
        cpu.shutdown();  
        memory.shutdown();  
        disk.shutdown();  
        System.out.println("computer closed!");  
    }  
} 

User 類如下:

[java] view plaincopy
public class User {  

    public static void main(String[] args) {  
        Computer computer = new Computer();  
        computer.startup();  
        computer.shutdown();  
    }  
} 

輸出:

start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!

如果我們沒有 Computer 類,那么,CPU、Memory、Disk 他們之間將會相互持有實例,產生關系,這樣會造成嚴重的依賴,修改一個類,可能會帶來其他類的修改,這不是我們想要看到的,有了 Computer 類,他們之間的關系被放在了 Computer 類里,這樣就起到了解耦的作用,這,就是外觀模式!

10、橋接模式(Bridge)

橋接模式就是把事物和其具體實現(xiàn)分開,使他們可以各自獨立的變化。橋接的用意是:將抽象化與實現(xiàn)化解耦,使得二者可以獨立變化,像我們常用的 JDBC 橋 DriverManager 一樣,JDBC 進行連接數(shù)據庫的時候,在各個數(shù)據庫之間進行切換,基本不需要動太多的代碼,甚至絲毫不用動,原因就是 JDBC 提供統(tǒng)一接口,每個數(shù)據庫提供各自的實現(xiàn),用一個叫做數(shù)據庫驅動的程序來橋接就行了。我們來看看關系圖:

http://wiki.jikexueyuan.com/project/java-special-topic/images/13.jpg" alt="" />

實現(xiàn)代碼:

先定義接口:

[java] view plaincopy
public interface Sourceable {  
    public void method();  
}

分別定義兩個實現(xiàn)類:

[java] view plaincopy
public class SourceSub1 implements Sourceable {  

    @Override  
    public void method() {  
        System.out.println("this is the first sub!");  
    }  
}  
[java] view plaincopy
public class SourceSub2 implements Sourceable {  

    @Override  
    public void method() {  
        System.out.println("this is the second sub!");  
    }  
}

定義一個橋,持有 Sourceable 的一個實例:

[java] view plaincopy
public abstract class Bridge {  
    private Sourceable source;  

    public void method(){  
        source.method();  
    }  

    public Sourceable getSource() {  
        return source;  
    }  

    public void setSource(Sourceable source) {  
        this.source = source;  
    }  
}  
[java] view plaincopy
public class MyBridge extends Bridge {  
    public void method(){  
        getSource().method();  
    }  
} 

測試類:

[java] view plaincopy
public class BridgeTest {  

    public static void main(String[] args) {  

        Bridge bridge = new MyBridge();  

        /*調用第一個對象*/  
        Sourceable source1 = new SourceSub1();  
        bridge.setSource(source1);  
        bridge.method();  

        /*調用第二個對象*/  
        Sourceable source2 = new SourceSub2();  
        bridge.setSource(source2);  
        bridge.method();  
    }  
}  
output:

this is the first sub!
this is the second sub!

這樣,就通過對 Bridge 類的調用,實現(xiàn)了對接口 Sourceable 的實現(xiàn)類 SourceSub1 和 SourceSub2 的調用。接下來我再畫個圖,大家就應該明白了,因為這個圖是我們 JDBC 連接的原理,有數(shù)據庫學習基礎的,一結合就都懂了。

http://wiki.jikexueyuan.com/project/java-special-topic/images/14.jpg" alt="" />

11、組合模式(Composite)

組合模式有時又叫部分-整體模式在處理類似樹形結構的問題時比較方便,看看關系圖:

http://wiki.jikexueyuan.com/project/java-special-topic/images/15.jpg" alt="" />

直接來看代碼:

[java] view plaincopy
public class TreeNode {  

    private String name;  
    private TreeNode parent;  
    private Vector<TreeNode> children = new Vector<TreeNode>();  

    public TreeNode(String name){  
        this.name = name;  
    }  

    public String getName() {  
        return name;  
    }  

    public void setName(String name) {  
        this.name = name;  
    }  

    public TreeNode getParent() {  
        return parent;  
    }  

    public void setParent(TreeNode parent) {  
        this.parent = parent;  
    }  

    //添加孩子節(jié)點  
    public void add(TreeNode node){  
        children.add(node);  
    }  

    //刪除孩子節(jié)點  
    public void remove(TreeNode node){  
        children.remove(node);  
    }  

    //取得孩子節(jié)點  
    public Enumeration<TreeNode> getChildren(){  
        return children.elements();  
    }  
}  
[java] view plaincopy
public class Tree {  

    TreeNode root = null;  

    public Tree(String name) {  
        root = new TreeNode(name);  
    }  

    public static void main(String[] args) {  
        Tree tree = new Tree("A");  
        TreeNode nodeB = new TreeNode("B");  
        TreeNode nodeC = new TreeNode("C");  

        nodeB.add(nodeC);  
        tree.root.add(nodeB);  
        System.out.println("build the tree finished!");  
    }  
} 

使用場景:將多個對象組合在一起進行操作,常用于表示樹形結構中,例如二叉樹,數(shù)等。

12、享元模式(Flyweight)

享元模式的主要目的是實現(xiàn)對象的共享,即共享池,當系統(tǒng)中對象多的時候可以減少內存的開銷,通常與工廠模式一起使用。

http://wiki.jikexueyuan.com/project/java-special-topic/images/16.jpg" alt="" />

FlyWeightFactory 負責創(chuàng)建和管理享元單元,當一個客戶端請求時,工廠需要檢查當前對象池中是否有符合條件的對象,如果有,就返回已經存在的對象,如果沒有,則創(chuàng)建一個新對象,F(xiàn)lyWeight 是超類。一提到共享池,我們很容易聯(lián)想到 Java 里面的 JDBC 連接池,想想每個連接的特點,我們不難總結出:適用于作共享的一些個對象,他們有一些共有的屬性,就拿數(shù)據庫連接池來說,url、driverClassName、username、password 及 dbname,這些屬性對于每個連接來說都是一樣的,所以就適合用享元模式來處理,建一個工廠類,將上述類似屬性作為內部數(shù)據,其它的作為外部數(shù)據,在方法調用時,當做參數(shù)傳進來,這樣就節(jié)省了空間,減少了實例的數(shù)量。

看個例子:

http://wiki.jikexueyuan.com/project/java-special-topic/images/17.jpg" alt="" />

看下數(shù)據庫連接池的代碼:

[java] view plaincopy
public class ConnectionPool {  

    private Vector<Connection> pool;  

    /*公有屬性*/  
    private String url = "jdbc:mysql://localhost:3306/test";  
    private String username = "root";  
    private String password = "root";  
    private String driverClassName = "com.mysql.jdbc.Driver";  

    private int poolSize = 100;  
    private static ConnectionPool instance = null;  
    Connection conn = null;  

    /*構造方法,做一些初始化工作*/  
    private ConnectionPool() {  
        pool = new Vector<Connection>(poolSize);  

        for (int i = 0; i < poolSize; i++) {  
            try {  
                Class.forName(driverClassName);  
                conn = DriverManager.getConnection(url, username, password);  
                pool.add(conn);  
            } catch (ClassNotFoundException e) {  
                e.printStackTrace();  
            } catch (SQLException e) {  
                e.printStackTrace();  
            }  
        }  
    }  

    /* 返回連接到連接池 */  
    public synchronized void release() {  
        pool.add(conn);  
    }  

    /* 返回連接池中的一個數(shù)據庫連接 */  
    public synchronized Connection getConnection() {  
        if (pool.size() > 0) {  
            Connection conn = pool.get(0);  
            pool.remove(conn);  
            return conn;  
        } else {  
            return null;  
        }  
    }  
}  

通過連接池的管理,實現(xiàn)了數(shù)據庫連接的共享,不需要每一次都重新創(chuàng)建連接,節(jié)省了數(shù)據庫重新創(chuàng)建的開銷,提升了系統(tǒng)的性能!本章講解了7種結構型模式,因為篇幅的問題,剩下的11種行為型模式,

本章是關于設計模式的最后一講,會講到第三種設計模式——行為型模式,共11種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。這段時間一直在寫關于設計模式的東西,終于寫到一半了,寫博文是個很費時間的東西,因為我得為讀者負責,不論是圖還是代碼還是表述,都希望能盡量寫清楚,以便讀者理解,我想不論是我還是讀者,都希望看到高質量的博文出來,從我本人出發(fā),我會一直堅持下去,不斷更新,源源動力來自于讀者朋友們的不斷支持,我會盡自己的努力,寫好每一篇文章!希望大家能不斷給出意見和建議,共同打造完美的博文!

先來張圖,看看這11中模式的關系:

第一類:通過父類與子類的關系進行實現(xiàn)。第二類:兩個類之間。第三類:類的狀態(tài)。第四類:通過中間類

http://wiki.jikexueyuan.com/project/java-special-topic/images/18.jpg" alt="" />

13、策略模式(strategy)

策略模式定義了一系列算法,并將每個算法封裝起來,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶。需要設計一個接口,為一系列實現(xiàn)類提供統(tǒng)一的方法,多個實現(xiàn)類實現(xiàn)該接口,設計一個抽象類(可有可無,屬于輔助類),提供輔助函數(shù),關系圖如下:

http://wiki.jikexueyuan.com/project/java-special-topic/images/19.jpg" alt="" />

圖中 ICalculator 提供同意的方法, AbstractCalculator 是輔助類,提供輔助方法,接下來,依次實現(xiàn)下每個類:

首先統(tǒng)一接口:

[java] view plaincopy
public interface ICalculator {  
    public int calculate(String exp);  
}

輔助類:

[java] view plaincopy
public abstract class AbstractCalculator {  

    public int[] split(String exp,String opt){  
        String array[] = exp.split(opt);  
        int arrayInt[] = new int[2];  
        arrayInt[0] = Integer.parseInt(array[0]);  
        arrayInt[1] = Integer.parseInt(array[1]);  
        return arrayInt;  
    }  
}

三個實現(xiàn)類:

[java] view plai