設(shè)計(jì)模式

單例模式的定義就不解釋過多了,相信很多小伙伴在設(shè)計(jì)的時(shí)候,都用到這個(gè)模式;常用的場景為 數(shù)據(jù)庫的訪問,文件流的訪問以及網(wǎng)絡(luò)連接池的訪問等等,在這些場景中,我們都希望實(shí)例只有一個(gè),除了減少內(nèi)存開銷之外,也防止多進(jìn)程修改文件錯(cuò)亂和數(shù)據(jù)庫鎖住的問題。
在這一篇文章中,我將帶你分析 android 常見的集中單例模式,并詳細(xì)分析他們的優(yōu)缺點(diǎn)。讓大家在以后的選擇單例中,可以根據(jù)實(shí)際情況選擇。
當(dāng)然,如有錯(cuò)誤,也歡迎指正。
下面是介紹:

1、餓漢式
就是初始化的時(shí)候,直接初始化,典型的以時(shí)間換空間的做法。

public class SingleMode{
    //構(gòu)造方法私有化,這樣外界就不能訪問了
    private SingleMode(){
    };
    //當(dāng)類被初始化的時(shí)候,就直接new出來
    private static SingleMode instance = new SingleMode();
    //提供一個(gè)方法,給他人調(diào)用
    public static SingleMode getInstance(){
        return instance;
    }

}

餓漢式的好處是線程安全,因?yàn)樘摂M機(jī)保證只會(huì)裝載一次,再裝載類的時(shí)候,是不會(huì)并發(fā)的,這樣就保證了線程安全的問題。
但缺點(diǎn)也很明顯,一初始化就實(shí)例占內(nèi)存了,但我褲子還沒脫,不想用呢。

2、懶漢式
為了解決上面的問題,開了懶漢式,就是需要使用的時(shí)候,才去加載;

public class SingleMode{
    //構(gòu)造方法私有化,這樣外界就不能訪問了
    private SingleMode(){
    };
    private static SingleMode mSingleMode;
    public static SingleModegetInstance(){ //這里就是延時(shí)加載的意思
        if (mSingleMode == null){
            mSingleMode = new SingleMode();
        }
        return  mSingleMode;
    }
}

懶漢式如上所示,
優(yōu)點(diǎn):

我們只需要在用到的時(shí)候,才申請內(nèi)存,且可以從外部獲取參數(shù)再實(shí)例化,這點(diǎn)是懶漢式的最大優(yōu)點(diǎn)了
缺點(diǎn):

單線程只實(shí)例了一次,如果是多線程了,那么它會(huì)被多次實(shí)例
至于問什么說它是線程不安全的呢?先下面這張圖:

我們假設(shè)一下,有兩個(gè)線程,A和B都要初始化這個(gè)實(shí)例;此時(shí) A 比較快,已經(jīng)判斷 mSingleMode 為null,正在創(chuàng)建實(shí)例,而 B 這時(shí)候也再判斷,但此時(shí) A 還沒有 new 完,所以 mSingleMode 還是為空的,所以B 也開始 new 出一個(gè)對象出來,這樣就相當(dāng)于創(chuàng)建了兩個(gè)實(shí)例了,所以,上面這種設(shè)計(jì)并不能保證線程安全。

2.1、如何實(shí)現(xiàn)懶漢式線程安全?
有人會(huì)說,簡單啊,你既然是線程并發(fā)不安全,那么加上一個(gè) synchronized 線程鎖不就完事了?但是這樣以來,會(huì)降低整個(gè)訪問速度,而且每次都要判斷,這個(gè)真的是我們想要的嗎?

由于上面的缺點(diǎn),所以,我們可以對上面的懶漢式加個(gè)優(yōu)化,如雙重檢查枷鎖:

public class SingleMode{
    //構(gòu)造方法私有化,這樣外界就不能訪問了
    private SingleMode(){
    };
    private static SingleMode mSingleMode;
    public static SingleMode getInstance(){
        if (mSingleMode == null){
           synchronized (SingleMode.class){
               if (mSingleMode == null){  //二次檢測
                   mSingleMode = new SingleMode();
               }
           }
        }
        return  mSingleMode;
    }
}

在上面的基礎(chǔ)上,用了二次檢查,這樣就保證了線程安全了,它會(huì)先判斷是否為null,是才會(huì)去加載,而且用 synchronized 修飾,則又保證了線程安全。

但是如果上面我們沒有用 volatile 修飾,它還是不安全的,有可能會(huì)出現(xiàn)null的問題。為什么?這是因?yàn)?java 在 new 一個(gè)對象的時(shí)候,它是無序的。而這個(gè)過程我們假設(shè)一下,假如有線程A,判斷為null了,這個(gè)時(shí)候它就進(jìn)入線程鎖了 mSingleMode = new SingleMode();,它不是一蹴而就,而是需要3步來完成的。

1、為 mSingleMode 創(chuàng)建內(nèi)存
2、new SingleMode() 調(diào)用這個(gè)構(gòu)造方法
3、mSingleMode 指向內(nèi)存區(qū)域
那你可能會(huì)有疑問,這樣不是很正常嗎?怎么會(huì)有 null 的情況?
非也,java 虛擬機(jī)在執(zhí)行上面這三步的時(shí)候,并不是按照這樣的順序來的,可能會(huì)打亂,這兒就是java重排序,比如2和3調(diào)換一下:

1、為 mSingleMode 創(chuàng)建內(nèi)存
3、mSingleMode 指向內(nèi)存區(qū)域
2、new SingleMode() 調(diào)用這個(gè)構(gòu)造方法
那這個(gè)時(shí)候,mSingleMode 已經(jīng)指向內(nèi)存區(qū)域了,那這個(gè)時(shí)候它就不為 null了,而實(shí)際上它并未獲得構(gòu)造方法,比如構(gòu)造方面里面有些參數(shù)或者方法,但是你并未獲取,然而這個(gè)時(shí)候線程B過來,而 mSingleMode已經(jīng)指向內(nèi)存區(qū)域不為空了,但方法和參數(shù)并未獲得, 所以,這樣你線程B在執(zhí)行 mSingleMode 的某些方法時(shí)就會(huì)報(bào)錯(cuò)。

當(dāng)然這種情況是非常少見的,不過還是暴露了這種問題所在。
所以我們用volatile 修飾,我們都知道 volatile 的一個(gè)重要屬性是可見性,即被 volatile 修飾的對象,在不同線程中是可以實(shí)時(shí)更新的,也是說線程A修改了某個(gè)被volatile修飾的值,那么線程B也知道它被修改了。但它還有另一個(gè)作用就是禁止java重排序的作用,這樣我們就不用擔(dān)心出現(xiàn)上面這種null 的情況了。如下:

public class SingleMode{
    //構(gòu)造方法私有化,這樣外界就不能訪問了
    private SingleMode(){
    };
    private volatile static SingleMode mSingleMode;
    public static SingleMode getInstance(){
        if (mSingleMode == null){
           synchronized (SingleMode.class){
               if (mSingleMode == null){  //二次檢測
                   mSingleMode = new SingleMode();
               }
           }
        }
        return  mSingleMode;
    }
}

看到這里,是不是感覺爬了幾百盤的坑,終于上了黃金段位了。。。
然而,并不是,你打了排位之后發(fā)現(xiàn)還是被吊打,所以我們可能還忽略了什么。
沒錯(cuò),這種方式,依舊存在缺點(diǎn):
由于volatile關(guān)鍵字會(huì)屏蔽虛擬機(jī)中一些必要的代碼優(yōu)化,所以運(yùn)行效率并不是很高。因此也建議,沒有特別的需要,不要大量使用。

筆者就遇到,使用這種模式,不知道什么原因,第二次進(jìn)入 activity的時(shí)候,view 刷不出來,然而數(shù)據(jù)對象什么的都存在,調(diào)得我心力交瘁,欲生欲死,最后換了其他單例模式就ok了,希望懂的大俠告訴我一下,我只能懷疑volatile了。。。。。。

那你都這樣說了,那還怎么玩,有沒有一種更好的方式呢?別急,往下看。

3、靜態(tài)式
什么叫靜態(tài)式呢?回顧一下上面的餓漢式,我們在剛開始的就初始化了,不管你需不需要,而我們也說過,Java 再裝載類的時(shí)候,是不會(huì)并發(fā)的,那么,我們能不能做到懶加載,即需要的時(shí)候再去初始化,又能保證線程安全呢?當(dāng)然可以,如下:

public class SingleMode{
    //構(gòu)造方法私有化,這樣外界就不能訪問了
    private SingleMode(){
    };
    public static class Holder{
        private static SingleMode mSingleMode = new SingleMode();
        public static SingleMode getInstance(){
            return  mSingleMode;
        }
    }
}

除了上面的餓漢式和懶漢式,,靜態(tài)的好處在于能保證線程安全,不用去考慮太多、缺點(diǎn)就在于對參數(shù)的傳遞比較不好。
那么這個(gè)時(shí)候,問題來了,參數(shù)怎么傳遞?這個(gè)確實(shí)沒懶漢式方便,不過沒關(guān)系,我們可以定義一個(gè)init()就可以了,只不過初始化的時(shí)候多了一行代碼;如:

public class SingleMode {
    //構(gòu)造方法私有化,這樣外界就不能訪問了
    private SingleMode(){
    };
    public static class Holder{
        private static SingleMode mSingleMode = new SingleMode();
        public static SingleMode  getInstance(){
            return  mSingleMode;
        }
    }
    private Context mContext;
    public void init(Context context){
        this.mContext = context;
    }
}

初始化:

 SingleMode mSingleMode = SingleMode.Holder.getInstance();
 mSingleMode.init(this);

1我的理解是:因?yàn)殪o態(tài)內(nèi)部類初始化時(shí)加的是類鎖,鎖住的是整個(gè)類,然后在這個(gè)階段初始化對象,只有一個(gè)線程能獲得這個(gè)類鎖,對象初始化的指令重排序也就無關(guān)緊要了。而前一個(gè)視頻是直接new一個(gè)單例實(shí)例,是初始化對象,這個(gè)階段jvm不加鎖。

對于為什么類初始化加鎖,對象初始化不加鎖?大概是類初始化要做的工作很多,而且只初始化一次,但對象初始化次數(shù)會(huì)很多,如果每次對象初始化都加鎖,消耗會(huì)很大。
2
4、枚舉單例
java 1.4 之前,我們習(xí)慣用靜態(tài)內(nèi)部類的方式來實(shí)現(xiàn)單例模式,但在1.5之后,在 《Effective java》也提到了這個(gè)觀點(diǎn),使用枚舉的優(yōu)點(diǎn)如下:

線程安全
延時(shí)加載
序列化和反序列化安全
所以,現(xiàn)在一般用單個(gè)枚舉的方式來實(shí)現(xiàn)單例,如上面,我們改一下:

public static SingleMode getInstance(){
        return Singleton.SINGLETON.getSingleTon();
    }
    public enum Singleton{
        SINGLETON ; //枚舉本身序列化之后返回的實(shí)例,名字隨便取
        private AppUninstallModel singleton;

        Singleton(){ //JVM保證只實(shí)例一次
            singleton = new AppUninstallModel();
        }
        // 公布對外方法
        public SingleMode getSingleTon(){
            return singleton;
        }
    }

好吧,這樣就ok了,但還是那個(gè)問題,初始化參數(shù)跟靜態(tài)類一樣,還是得重新寫個(gè) init() 有失必有得吧。

這樣,我們的單例模式就學(xué)完了。
————————————————
版權(quán)聲明:本文為CSDN博主「夏至的稻穗」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u011418943/article/details/60139644

Num1:單例模式

基本概念:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。

常見寫法:

懶漢式

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

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

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

調(diào)用:

Singleton.getInstance().method();  

優(yōu)點(diǎn):延遲加載(需要的時(shí)候才去加載),適合單線程操作
缺點(diǎn): 線程不安全,在多線程中很容易出現(xiàn)不同步的情況,如在數(shù)據(jù)庫對象進(jìn)行的頻繁讀寫操作時(shí)。

雙重線程檢查模式

public class SingletonInner {  
    private static volatile SingletonInner sInst = null;  // <<< 這里添加了 volatile  

    /** 
     * 私有的構(gòu)造函數(shù) 
     */  
    private SingletonInner() {}  

    public static SingletonInner getInstance() {  
        SingletonInner inst = sInst;  // <<< 在這里創(chuàng)建臨時(shí)變量
        if (inst == null) {
            synchronized (SingletonInner.class) {
                inst = sInst;
                if (inst == null) {
                    inst = new SingletonInner();
                    sInst = inst;
                }
            }
        }
        return inst;  // <<< 注意這里返回的是臨時(shí)變量
    }

    protected void method() {  
        System.out.println("SingletonInner");  
    }  
}  

調(diào)用:

Singleton.getInstance().method();  

優(yōu)點(diǎn):延遲加載,線程安全
缺點(diǎn): 寫法復(fù)雜,不簡潔

內(nèi)部類的實(shí)現(xiàn)

public class SingletonInner {  
    /** 
     * 內(nèi)部類實(shí)現(xiàn)單例模式 
     * 延遲加載,減少內(nèi)存開銷   
     */  
    private static class SingletonHolder {  
        private static SingletonInner instance = new SingletonInner();  
    }  

    /** 
     * 私有的構(gòu)造函數(shù) 
     */  
    private SingletonInner() {}  

    public static SingletonInner getInstance() {  
        return SingletonHolder.instance;  
    }  

    protected void method() {  
        System.out.println("SingletonInner");  
    }  
}  

調(diào)用:

Singleton.getInstance().method();  

優(yōu)點(diǎn):延遲加載,線程安全(java中class加載時(shí)互斥的),也減少了內(nèi)存消耗,推薦使用內(nèi)部類方式。

Num2:工廠模式

基本概念:為創(chuàng)建對象提供過渡接口,以便將創(chuàng)建對象的具體過程屏蔽隔離起來,達(dá)到提高靈活性的目的。

分為三類:

  • 簡單工廠模式Simple Factory:不利于產(chǎn)生系列產(chǎn)品;

  • 工廠方法模式Factory Method:又稱為多形性工廠;

  • 抽象工廠模式Abstract Factory:又稱為工具箱,產(chǎn)生產(chǎn)品族,但不利于產(chǎn)生新的產(chǎn)品;

這三種模式從上到下逐步抽象,并且更具一般性。GOF在《設(shè)計(jì)模式》一書中將工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory)。將簡單工廠模式(Simple Factory)看為工廠方法模式的一種特例,兩者歸為一類。

簡單工廠模式

簡單工廠模式又稱靜態(tài)工廠方法模式。從命名上就可以看出這個(gè)模式一定很簡單。它存在的目的很簡單:定義一個(gè)用于創(chuàng)建對象的接口。

在簡單工廠模式中,一個(gè)工廠類處于對產(chǎn)品類實(shí)例化調(diào)用的中心位置上,它決定那一個(gè)產(chǎn)品類應(yīng)當(dāng)被實(shí)例化, 如同一個(gè)交通警察站在來往的車輛流中,決定放行那一個(gè)方向的車輛向那一個(gè)方向流動(dòng)一樣。

先來看看它的組成:

  • 工廠類角色:這是本模式的核心,含有一定的商業(yè)邏輯和判斷邏輯。在java中它往往由一個(gè)具體類實(shí)現(xiàn)。
  • 抽象產(chǎn)品角色:它一般是具體產(chǎn)品繼承的父類或者實(shí)現(xiàn)的接口。在java中由接口或者抽象類來實(shí)現(xiàn)。
  • 具體產(chǎn)品角色:工廠類所創(chuàng)建的對象就是此角色的實(shí)例。在java中由一個(gè)具體類實(shí)現(xiàn)。

示例代碼:

public class Factory{ //getClass 產(chǎn)生Sample 一般可使用動(dòng)態(tài)類裝載裝入類。
    public static Sample creator(int which){ 
        if (which==1)
            return new SampleA();
        else if (which==2)
            return new SampleB();
    }
}

還有一種目前比較流行的規(guī)范是把靜態(tài)工廠方法命名為valueOf或者getInstance。

valueOf:該方法返回的實(shí)例與它的參數(shù)具有同樣的值,例如:

int a=Integer.valueOf(100); //返回取值為100的Integer對象

從上面代碼可以看出,valueOf()方法能執(zhí)行類型轉(zhuǎn)換操作,在本例中,把int類型的基本數(shù)據(jù)轉(zhuǎn)換為Integer對象。

public class Complex {
    private final float re;
    private final float im;

    private Complex(float re, float im){
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(float re, float im){
        return new Complex(re, im);
    }

    public static Complex valueOfPolar(float r, float theta){
        return new Complex((float)(r * Math.cos(theta)), (float)(r * Math.sin(theta)));
    }
}

getInstance:返回的實(shí)例與參數(shù)匹配,例如:

Calendar cal=Calendar.getInstance(Locale.CHINA); //返回符合中國標(biāo)準(zhǔn)的日歷

工廠方法模式

工廠方法模式是簡單工廠模式的進(jìn)一步抽象化和推廣,工廠方法模式里不再只由一個(gè)工廠類決定那一個(gè)產(chǎn)品類應(yīng)當(dāng)被實(shí)例化,這個(gè)決定被交給抽象工廠的子類去做。
來看下它的組成:

  • 抽象工廠角色: 這是工廠方法模式的核心,它與應(yīng)用程序無關(guān)。是具體工廠角色必須實(shí)現(xiàn)的接口或者必須繼承的父類。在java中它由抽象類或者接口來實(shí)現(xiàn)。
  • 具體工廠角色:它含有和具體業(yè)務(wù)邏輯有關(guān)的代碼。由應(yīng)用程序調(diào)用以創(chuàng)建對應(yīng)的具體產(chǎn)品的對象
  • 抽象產(chǎn)品角色:它是具體產(chǎn)品繼承的父類或者是實(shí)現(xiàn)的接口。在java中一般有抽象類或者接口來實(shí)現(xiàn)。
  • 具體產(chǎn)品角色:具體工廠角色所創(chuàng)建的對象就是此角色的實(shí)例。在java中由具體的類來實(shí)現(xiàn)。

工廠方法模式使用繼承自抽象工廠角色的多個(gè)子類來代替簡單工廠模式中的“上帝類”。正如上面所說,這樣便分擔(dān)了對象承受的壓力;而且這樣使得結(jié)構(gòu)變得靈活 起來——當(dāng)有新的產(chǎn)品(即暴發(fā)戶的汽車)產(chǎn)生時(shí),只要按照抽象產(chǎn)品角色、抽象工廠角色提供的合同來生成,那么就可以被客戶使用,而不必去修改任何已有的代 碼。可以看出工廠角色的結(jié)構(gòu)也是符合開閉原則的!

示例代碼:

//抽象產(chǎn)品角色
public interface Moveable {
    void run();
}
//具體產(chǎn)品角色
public class Plane implements Moveable {
    @Override
    public void run() {
        System.out.println("plane....");
    }
}
//具體產(chǎn)品角色
public class Broom implements Moveable {
    @Override
    public void run() {
        System.out.println("broom.....");
    }
}

//抽象工廠
public abstract class VehicleFactory {
    abstract Moveable create();
}
//具體工廠
public class PlaneFactory extends VehicleFactory{
    public Moveable create() {
        return new Plane();
    }
}
//具體工廠
public class BroomFactory extends VehicleFactory{
    public Moveable create() {
        return new Broom();
    }
}
//測試類
public class Test {
    public static void main(String[] args) {
        VehicleFactory factory = new BroomFactory();
        Moveable m = factory.create();
        m.run();
    }
}

可以看出工廠方法的加入,使得對象的數(shù)量成倍增長。當(dāng)產(chǎn)品種類非常多時(shí),會(huì)出現(xiàn)大量的與之對應(yīng)的工廠對象,這不是我們所希望的。因?yàn)槿绻荒鼙苊膺@種情 況,可以考慮使用簡單工廠模式與工廠方法模式相結(jié)合的方式來減少工廠類:即對于產(chǎn)品樹上類似的種類(一般是樹的葉子中互為兄弟的)使用簡單工廠模式來實(shí) 現(xiàn)。

簡單工廠和工廠方法模式的比較

工廠方法模式和簡單工廠模式在定義上的不同是很明顯的。工廠方法模式的核心是一個(gè)抽象工廠類,而不像簡單工廠模式, 把核心放在一個(gè)實(shí)類上。工廠方法模式可以允許很多實(shí)的工廠類從抽象工廠類繼承下來, 從而可以在實(shí)際上成為多個(gè)簡單工廠模式的綜合,從而推廣了簡單工廠模式。
反過來講,簡單工廠模式是由工廠方法模式退化而來。設(shè)想如果我們非常確定一個(gè)系統(tǒng)只需要一個(gè)實(shí)的工廠類, 那么就不妨把抽象工廠類合并到實(shí)的工廠類中去。而這樣一來,我們就退化到簡單工廠模式了。

抽象工廠模式

示例代碼:

//抽象工廠類
public abstract class AbstractFactory {
    public abstract Vehicle createVehicle();
    public abstract Weapon createWeapon();
    public abstract Food createFood();
}
//具體工廠類,其中Food,Vehicle,Weapon是抽象類,
public class DefaultFactory extends AbstractFactory{
    @Override
    public Food createFood() {
        return new Apple();
    }
    @Override
    public Vehicle createVehicle() {
        return new Car();
    }
    @Override
    public Weapon createWeapon() {
        return new AK47();
    }
}
//測試類
public class Test {
    public static void main(String[] args) {
        AbstractFactory f = new DefaultFactory();
        Vehicle v = f.createVehicle();
        v.run();
        Weapon w = f.createWeapon();
        w.shoot();
        Food a = f.createFood();
        a.printName();
    }
}

在抽象工廠模式中,抽象產(chǎn)品 (AbstractProduct) 可能是一個(gè)或多個(gè),從而構(gòu)成一個(gè)或多個(gè)產(chǎn)品族(Product Family)。 在只有一個(gè)產(chǎn)品族的情況下,抽象工廠模式實(shí)際上退化到工廠方法模式。

總結(jié)

  1. 簡單工廠模式是由一個(gè)具體的類去創(chuàng)建其他類的實(shí)例,父類是相同的,父類是具體的。
  2. 工廠方法模式是有一個(gè)抽象的父類定義公共接口,子類負(fù)責(zé)生成具體的對象,這樣做的目的是將類的實(shí)例化操作延遲到子類中完成。
  3. 抽象工廠模式提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無須指定他們具體的類。它針對的是有多個(gè)產(chǎn)品的等級結(jié)構(gòu)。而工廠方法模式針對的是一個(gè)產(chǎn)品的等級結(jié)構(gòu)。

Num3:建造(Builder)模式

基本概念:是一種對象構(gòu)建的設(shè)計(jì)模式,它可以將復(fù)雜對象的建造過程抽象出來(抽象類別),使這個(gè)抽象過程的不同實(shí)現(xiàn)方法可以構(gòu)造出不同表現(xiàn)(屬性)的對象。

Builder模式是一步一步創(chuàng)建一個(gè)復(fù)雜的對象,它允許用戶可以只通過指定復(fù)雜對象的類型和內(nèi)容就可以構(gòu)建它們。用戶不知道內(nèi)部的具體構(gòu)建細(xì)節(jié)。Builder模式是非常類似抽象工廠模式,細(xì)微的區(qū)別大概只有在反復(fù)使用中才能體會(huì)到。

UML結(jié)構(gòu)圖:

[圖片上傳失敗...(image-5280f9-1685673732847)]

上圖是Strategy 模式的結(jié)構(gòu)圖,讓我們可以進(jìn)行更方便的描述:

  • Builder:為創(chuàng)建一個(gè)Product對象的各個(gè)部件指定抽象接口。

  • ConcreteBuilder:實(shí)現(xiàn)Builder的接口以構(gòu)造和裝配該產(chǎn)品的各個(gè)部件,定義并明確它所創(chuàng)建的表示,提供一個(gè)檢索產(chǎn)品的接口

  • Director:構(gòu)造一個(gè)使用Builder接口的對象。

  • Product:表示被構(gòu)造的復(fù)雜對象。ConcreateBuilder創(chuàng)建該產(chǎn)品的內(nèi)部表示并定義它的裝配過程。

為何使用

是為了將構(gòu)建復(fù)雜對象的過程和它的部件解耦。注意:是解耦過程和部件。
因?yàn)橐粋€(gè)復(fù)雜的對象,不但有很多大量組成部分,如汽車,有很多部件:車輪、方向盤、發(fā)動(dòng)機(jī),還有各種小零件等等,部件很多,但遠(yuǎn)不止這些,如何將這些部件裝配成一輛汽車,這個(gè)裝配過程也很復(fù)雜(需要很好的組裝技術(shù)),Builder模式就是為了將部件和組裝過程分開。

如何使用

首先假設(shè)一個(gè)復(fù)雜對象是由多個(gè)部件組成的,Builder模式是把復(fù)雜對象的創(chuàng)建和部件的創(chuàng)建分別開來,分別用Builder類和Director類來表示。

首先,需要一個(gè)接口,它定義如何創(chuàng)建復(fù)雜對象的各個(gè)部件:

public interface Builder {
   //創(chuàng)建部件A  比如創(chuàng)建汽車車輪void buildPartA();
   //創(chuàng)建部件B 比如創(chuàng)建汽車方向盤void buildPartB();
   //創(chuàng)建部件C 比如創(chuàng)建汽車發(fā)動(dòng)機(jī)void buildPartC();
   //返回最后組裝成品結(jié)果 (返回最后裝配好的汽車)
   //成品的組裝過程不在這里進(jìn)行,而是轉(zhuǎn)移到下面的Director類中進(jìn)行.
   //從而實(shí)現(xiàn)了解耦過程和部件
    Product getResult();
}

用Director構(gòu)建最后的復(fù)雜對象,而在上面Builder接口中封裝的是如何創(chuàng)建一個(gè)個(gè)部件(復(fù)雜對象是由這些部件組成的),也就是說Director的內(nèi)容是如何將部件最后組裝成成品:

public class Director {
    private Builder builder;
    public Director( Builder builder ) {
        this.builder = builder;
   }
   // 將部件partA partB partC最后組成復(fù)雜對象
   //這里是將車輪 方向盤和發(fā)動(dòng)機(jī)組裝成汽車的過程
    public void construct() {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}

Builder的具體實(shí)現(xiàn)ConcreteBuilder:

  • 通過具體完成接口Builder來構(gòu)建或裝配產(chǎn)品的部件;
  • 定義并明確它所要?jiǎng)?chuàng)建的是什么具體東西;
  • 提供一個(gè)可以重新獲取產(chǎn)品的接口。
public class ConcreteBuilder implements Builder {
 Part partA, partB, partC;
 public void buildPartA() {
  //這里是具體如何構(gòu)建
 }
 public void buildPartB() {
  //這里是具體如何構(gòu)建
 }
 public void buildPartC() {
  //這里是具體如何構(gòu)建
 }
 public Product getResult() {
  //返回最后組裝成品結(jié)果
 }
}

復(fù)雜對象:產(chǎn)品Product:

public interface Product { }

復(fù)雜對象的部件:

public interface Part { }

我們看看如何調(diào)用Builder模式:

ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();

Builder模式的應(yīng)用

在Java實(shí)際使用中,我們經(jīng)常用到"池"(Pool)的概念,當(dāng)資源提供者無法提供足夠的資源,并且這些資源需要被很多用戶反復(fù)共享時(shí),就需要使用池。"池"實(shí)際是一段內(nèi)存,當(dāng)池中有一些復(fù)雜的資源的"斷肢"(比如數(shù)據(jù)庫的連接池,也許有時(shí)一個(gè)連接會(huì)中斷),如果循環(huán)再利用這些"斷肢",將提高內(nèi)存使用效率,提高池的性能。修改Builder模式中Director類使之能診斷"斷肢"斷在哪個(gè)部件上,再修復(fù)這個(gè)部件。

Num4:觀察者模式

基本概念:觀察者模式定義了一種一對多的依賴關(guān)系,讓多個(gè)觀察者對象同時(shí)監(jiān)聽某一主題對象。這個(gè)主題對象在狀態(tài)發(fā)生變化時(shí),會(huì)通知所有觀察者對象,使它們能夠自動(dòng)更新自己。觀察者模式又叫發(fā)布-訂閱(Publish/Subscribe)模式。

UML結(jié)構(gòu)圖

[圖片上傳失敗...(image-5d07fe-1685673732844)]

上圖是Observer 模式的結(jié)構(gòu)圖,讓我們可以進(jìn)行更方便的描述:

  • Subject類:它把所有對觀察者對象的引用保存在一個(gè)聚集里,每個(gè)主題都可以有任何數(shù)量的觀察著。抽象主題提供一個(gè)接口,可以增加和刪除觀察著對象。

  • Observer類:抽象觀察者,為所有的具體觀察者定義一個(gè)接口,在得到主題的通知時(shí)更新自己。

  • ConcreteSubject類:具體主題,將有關(guān)狀態(tài)存入具體觀察者對象;在具體主題的內(nèi)部狀態(tài)改變時(shí),給所有登記過的觀察者發(fā)出通知。

  • ConcreteObserver類:具體觀察者,實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。

如何使用

例如:老師有電話號碼,學(xué)生需要知道老師的電話號碼以便于在合適的時(shí)候撥打,在這樣的組合中,老師就是一個(gè)被觀察者(Subject),學(xué)生就是需要知道信息的觀察者,當(dāng)老師的電話號碼發(fā)生改變時(shí),學(xué)生得到通知,并更新相應(yīng)的電話記錄。

先創(chuàng)建一個(gè)Subject類:

/**  
 * Subject(目標(biāo),Subject):    
 * 目標(biāo)知道它的觀察者??梢杂腥我舛鄠€(gè)觀察者觀察同一個(gè)目標(biāo)。  
 * 提供注冊和刪除觀察者對象的接口。  
 */  
public interface Subject {  
    public void attach(Observer mObserver);  
    public void detach(Observer mObserver);        
    public void notice();  
} 

創(chuàng)建Observer類:

/**  
 * Observer(觀察者,Observer):  
 * 為那些在目標(biāo)發(fā)生改變時(shí)需要獲得通知的對象定義一個(gè)更新接口。   
 */  
public interface Observer {  
    public void update();  
}  

創(chuàng)建ConcreteSubject類:

/**  
 * ConcreteSubject(具體目標(biāo),Teacher)  
 * 將有關(guān)狀態(tài)存入各ConcreteObserve對象。  
 * 當(dāng)他的狀態(tài)發(fā)生改變時(shí),向他的各個(gè)觀察者發(fā)出通知。   
 */  
public class Teacher implements Subject{  

    private String phone;  
    private Vector students;  

    public Teacher(){  
        phone = "";  
        students = new Vector();  
    }  

    @Override  
    public void attach(Observer mObserver) {  
        students.add(mObserver);  
    }  

    @Override  
    public void detach(Observer mObserver) {  
        students.remove(mObserver);  
    }  

    @Override  
    public void notice() {  
        for(int i=0;i<students.size();i++){  
            ((Observer)students.get(i)).update();  
        }  
    }  

    public String getPhone() {  
        return phone;  
    }  

    public void setPhone(String phone) {  
        this.phone = phone;  
        notice();  
    }  
}

創(chuàng)建ConcreteObserver類:

/**  
 * ConcreteObserver(具體觀察者, Student):  
 * 維護(hù)一個(gè)指向ConcreteSubject對象的引用。  
 * 存儲(chǔ)有關(guān)狀態(tài),這些狀態(tài)應(yīng)與目標(biāo)的狀態(tài)保持一致。  
 * 實(shí)現(xiàn)Observer的更新接口以使自身狀態(tài)與目標(biāo)的狀態(tài)保持一致。  
 */  
public class Student implements Observer{  

    private String name;  
    private String phone;  
    private Teacher mTeacher;  

    public Student(String name,Teacher t){  
       this.name = name;  
       mTeacher = t;  
    }  

    public void show(){  
       System.out.println("Name:"+name+"\nTeacher'sphone:" + phone);  
    }  

    @Override  
    public void update() {  
        phone = mTeacher.getPhone();  
    }  
}  

客戶端測試:

 /**  
 * 觀察者(Observer)模式測試類   
 */  
public class ObserverClient {  
    public static void main(String[] args) {  
       Vector students = new Vector();  
       Teacher t = new Teacher();  
       for(int i= 0;i<10;i++){  
           Student st = new Student("Andy.Chen"+i,t);  
           students.add(st);  
           t.attach(st);  
       }  

       System.out.println("Welcome to Andy.Chen Blog!" +"\n"   
                   +"Observer Patterns." +"\n"  
                   +"-------------------------------");  

       t.setPhone("12345678");  
       for(int i=0;i<3;i++)  
           ((Student)students.get(i)).show();  

       t.setPhone("87654321");  
       for(int i=0;i<3;i++)  
           ((Student)students.get(i)).show();  
    }  
} 

程序運(yùn)行結(jié)果如下:

Welcome to Andy.Chen Blog!  
Observer Patterns.  
-------------------------------  
Name:Andy.Chen0  
Teacher'sphone:12345678  
Name:Andy.Chen1  
Teacher'sphone:12345678  
Name:Andy.Chen2  
Teacher'sphone:12345678  
Name:Andy.Chen0  
Teacher'sphone:87654321  
Name:Andy.Chen1  
Teacher'sphone:87654321  
Name:Andy.Chen2  
Teacher'sphone:87654321  

總結(jié)

觀察者模式何時(shí)適用?

  • 當(dāng)一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面依賴于另一方面。將這二者封裝在獨(dú)立的對象中可以使他們各自獨(dú)立地改變和復(fù)用。

  • 當(dāng)對一個(gè)對象的改變需要同時(shí)改變其它對象,而不知道具體由多少對象有待改變。

  • 當(dāng)一個(gè)對象必須通知其他對象,而它又不能假定其他對象是誰,換言之,你不希望這些對象是緊密耦合的。讓耦合的雙方都依賴于抽象,而不是依賴于具體。

Num5:適配器(Adapter)模式

基本概念:適配器模式把一個(gè)類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個(gè)類能夠在一起工作。

適配器模式的用途

用電器做例子,筆記本電腦的插頭一般都是三相的,即除了陽極、陰極外,還有一個(gè)地極。而有些地方的電源插座卻只有兩極,沒有地極。電源插座與筆記本電腦的電源插頭不匹配使得筆記本電腦無法使用。這時(shí)候一個(gè)三相到兩相的轉(zhuǎn)換器(適配器)就能解決此問題,而這正像是本模式所做的事情。

適配器模式的結(jié)構(gòu)
適配器模式有類的適配器模式對象的適配器模式兩種不同的形式。

類適配器模式:

[圖片上傳失敗...(image-b90b22-1685673732844)]

在上圖中可以看出,Adaptee類并沒有sampleOperation2()方法,而客戶端則期待這個(gè)方法。為使客戶端能夠使用Adaptee類,提供一個(gè)中間環(huán)節(jié),即類Adapter,把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是繼承關(guān)系,這決定了這個(gè)適配器模式是類的:

  • 目標(biāo)(Target)角色:這就是所期待得到的接口。注意:由于這里討論的是類適配器模式,因此目標(biāo)不可以是類。
  • 源(Adapee)角色:現(xiàn)在需要適配的接口。
  • 適配器(Adaper)角色:適配器類是本模式的核心。適配器把源接口轉(zhuǎn)換成目標(biāo)接口。顯然,這一角色不可以是接口,而必須是具體類。

示例代碼:

public interface Target {
    /**
     * 這是源類Adaptee也有的方法
     */
    public void sampleOperation1(); 
    /**
     * 這是源類Adapteee沒有的方法
     */
    public void sampleOperation2(); 
}

上面給出的是目標(biāo)角色的源代碼,這個(gè)角色是以一個(gè)Java接口的形式實(shí)現(xiàn)的??梢钥闯觯@個(gè)接口聲明了兩個(gè)方法:sampleOperation1()和sampleOperation2()。而源角色Adaptee是一個(gè)具體類,它有一個(gè)sampleOperation1()方法,但是沒有sampleOperation2()方法。

public class Adaptee {
    public void sampleOperation1(){}
}

適配器角色Adapter擴(kuò)展了Adaptee,同時(shí)又實(shí)現(xiàn)了目標(biāo)(Target)接口。由于Adaptee沒有提供sampleOperation2()方法,而目標(biāo)接口又要求這個(gè)方法,因此適配器角色Adapter實(shí)現(xiàn)了這個(gè)方法。

public class Adapter extends Adaptee implements Target {
    /**
     * 由于源類Adaptee沒有方法sampleOperation2()
     * 因此適配器補(bǔ)充上這個(gè)方法
     */
    @Override
    public void sampleOperation2() {
        //寫相關(guān)的代碼
    }
}

對象適配器模式:

[圖片上傳失敗...(image-a6b0ac-1685673732844)]

從上圖可以看出,Adaptee類并沒有sampleOperation2()方法,而客戶端則期待這個(gè)方法。為使客戶端能夠使用Adaptee類,需要提供一個(gè)包裝(Wrapper)類Adapter。這個(gè)包裝類包裝了一個(gè)Adaptee的實(shí)例,從而此包裝類能夠把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是委派關(guān)系,這決定了適配器模式是對象的。

示例代碼:

public interface Target {
    /**
     * 這是源類Adaptee也有的方法
     */
    public void sampleOperation1(); 
    /**
     * 這是源類Adapteee沒有的方法
     */
    public void sampleOperation2(); 
}

public class Adaptee {
    public void sampleOperation1(){}
}

適配器類:

public class Adapter {
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee){
        this.adaptee = adaptee;
    }
    /**
     * 源類Adaptee有方法sampleOperation1
     * 因此適配器類直接委派即可
     */
    public void sampleOperation1(){
        this.adaptee.sampleOperation1();
    }
    /**
     * 源類Adaptee沒有方法sampleOperation2
     * 因此由適配器類需要補(bǔ)充此方法
     */
    public void sampleOperation2(){
        //寫相關(guān)的代碼
    }
}

類適配器和對象適配器的權(quán)衡

  • 類適配器使用對象繼承的方式,是靜態(tài)的定義方式;而對象適配器使用對象組合的方式,是動(dòng)態(tài)組合的方式。

  • 對于類適配器由于適配器直接繼承了Adaptee,使得適配器不能和Adaptee的子類一起工作,因?yàn)槔^承是靜態(tài)的關(guān)系,當(dāng)適配器繼承了Adaptee后,就不可能再去處理 Adaptee的子類了。

  • 對于對象適配器一個(gè)適配器可以把多種不同的源適配到同一個(gè)目標(biāo)。換言之,同一個(gè)適配器可以把源類和它的子類都適配到目標(biāo)接口。因?yàn)閷ο筮m配器采用的是對象組合的關(guān)系,只要對象類型正確,是不是子類都無所謂。

  • 對于類適配器適配器可以重定義Adaptee的部分行為,相當(dāng)于子類覆蓋父類的部分實(shí)現(xiàn)方法。

  • 對于對象適配器要重定義Adaptee的行為比較困難,這種情況下,需要定義Adaptee的子類來實(shí)現(xiàn)重定義,然后讓適配器組合子類。雖然重定義Adaptee的行為比較困難,但是想要增加一些新的行為則方便的很,而且新增加的行為可同時(shí)適用于所有的源。

  • 對于類適配器,僅僅引入了一個(gè)對象,并不需要額外的引用來間接得到Adaptee。

  • 對于對象適配器,需要額外的引用來間接得到Adaptee。

建議盡量使用對象適配器的實(shí)現(xiàn)方式,多用合成或聚合、少用繼承。當(dāng)然,具體問題具體分析,根據(jù)需要來選用實(shí)現(xiàn)方式,最適合的才是最好的。

適配器模式的優(yōu)點(diǎn)

  • 更好的復(fù)用性:系統(tǒng)需要使用現(xiàn)有的類,而此類的接口不符合系統(tǒng)的需要。那么通過適配器模式就可以讓這些功能得到更好的復(fù)用。

  • 更好的擴(kuò)展性:在實(shí)現(xiàn)適配器功能的時(shí)候,可以調(diào)用自己開發(fā)的功能,從而自然地?cái)U(kuò)展系統(tǒng)的功能。

適配器模式的缺點(diǎn)

過多的使用適配器,會(huì)讓系統(tǒng)非常零亂,不易整體進(jìn)行把握。比如,明明看到調(diào)用的是A接口,其實(shí)內(nèi)部被適配成了B接口的實(shí)現(xiàn),一個(gè)系統(tǒng)如果太多出現(xiàn)這種情況,無異于一場災(zāi)難。因此如果不是很有必要,可以不使用適配器,而是直接對系統(tǒng)進(jìn)行重構(gòu)。

Num6:代理模式

基本概念:為其他對象提供一種代理以控制對這個(gè)對象的訪問。也可以說,在出發(fā)點(diǎn)到目的地之間有一道中間層,意為代理。

為什么要使用

  • 授權(quán)機(jī)制不同級別的用戶對同一對象擁有不同的訪問權(quán)利,如在論壇系統(tǒng)中,就使用Proxy進(jìn)行授權(quán)機(jī)制控制,訪問論壇有兩種人:注冊用戶和游客(未注冊用戶),論壇就通過類似ForumProxy這樣的代理來控制這兩種用戶對論壇的訪問權(quán)限。

  • 某個(gè)客戶端不能直接操作到某個(gè)對象,但又必須和那個(gè)對象有所互動(dòng)。

舉例兩個(gè)具體情況:

  • 如果那個(gè)對象是一個(gè)是很大的圖片,需要花費(fèi)很長時(shí)間才能顯示出來,那么當(dāng)這個(gè)圖片包含在文檔中時(shí),使用編輯器或?yàn)g覽器打開這個(gè)文檔,打開文檔必須很迅速,不能等待大圖片處理完成,這時(shí)需要做個(gè)圖片Proxy來代替真正的圖片。
  • 如果那個(gè)對象在Internet的某個(gè)遠(yuǎn)端服務(wù)器上,直接操作這個(gè)對象因?yàn)榫W(wǎng)絡(luò)速度原因可能比較慢,那我們可以先用Proxy來代替那個(gè)對象。

總之原則是,對于開銷很大的對象,只有在使用它時(shí)才創(chuàng)建,這個(gè)原則可以為我們節(jié)省很多寶貴的Java內(nèi)存。所以,有些人認(rèn)為Java耗費(fèi)資源內(nèi)存,我以為這和程序編制思路也有一定的關(guān)系。

如何使用

以論壇系統(tǒng)為例,訪問論壇系統(tǒng)的用戶有多種類型:注冊普通用戶、論壇管理者、系統(tǒng)管理者、游客。注冊普通用戶才能發(fā)言,論壇管理者可以管理他被授權(quán)的論壇,系統(tǒng)管理者可以管理所有事務(wù)等,這些權(quán)限劃分和管理是使用Proxy完成的。
在Forum中陳列了有關(guān)論壇操作的主要行為,如論壇名稱,論壇描述的獲取和修改,帖子發(fā)表刪除編輯等,在ForumPermissions中定義了各種級別權(quán)限的用戶:

public class ForumPermissions implements Cacheable {
    /**
    * Permission to read object.
    */
    public static final int READ = 0;

    /**
    * Permission to administer the entire sytem.
    */
    public static final int SYSTEM_ADMIN = 1;

    /**
    * Permission to administer a particular forum.
    */
    public static final int FORUM_ADMIN = 2;

    /**
    * Permission to administer a particular user.
    */
    public static final int USER_ADMIN = 3;

    /**
    * Permission to administer a particular group.
    */
    public static final int GROUP_ADMIN = 4;

    /**
    * Permission to moderate threads.
    */
    public static final int MODERATE_THREADS = 5;

    /**
    * Permission to create a new thread.
    */
    public static final int CREATE_THREAD = 6;

    /**
    * Permission to create a new message.
    */
    public static final int CREATE_MESSAGE = 7;

    /**
    * Permission to moderate messages.
    */
    public static final int MODERATE_MESSAGES = 8;

    public boolean isSystemOrForumAdmin() {
        return (values[FORUM_ADMIN] || values[SYSTEM_ADMIN]);
    }

//相關(guān)操作代碼
}

因此,F(xiàn)orum中各種操作權(quán)限是和ForumPermissions定義的用戶級別有關(guān)系的,作為接口Forum的實(shí)現(xiàn):ForumProxy正是將這種對應(yīng)關(guān)系聯(lián)系起來。比如,修改Forum的名稱,只有論壇管理者或系統(tǒng)管理者可以修改,代碼如下:

public class ForumProxy implements Forum {
    private ForumPermissions permissions;
    private Forum forum;
    this.authorization = authorization;

    public ForumProxy(Forum forum, Authorization authorization,ForumPermissions permissions){
        this.forum = forum;
        this.authorization = authorization;
        this.permissions = permissions;
    }
    .....
    public void setName(String name) throws UnauthorizedException,
        ForumAlreadyExistsException{
        //只有是系統(tǒng)或論壇管理者才可以修改名稱
      if (permissions.isSystemOrForumAdmin()) {
        forum.setName(name);
      }
    else {
    throw new UnauthorizedException();
    }
    }
    ...

} 

而DbForum才是接口Forum的真正實(shí)現(xiàn),以修改論壇名稱為例:

public class DbForum implements Forum, Cacheable {
    ...
    public void setName(String name) throws ForumAlreadyExistsException {
  ....
        this.name = name;
       //這里真正將新名稱保存到數(shù)據(jù)庫中
       saveToDb();
  ....
    }
    ...
}

凡是涉及到對論壇名稱修改這一事件,其他程序都首先得和ForumProxy打交道,由ForumProxy決定是否有權(quán)限做某一樣事情,F(xiàn)orumProxy是個(gè)名副其實(shí)的"網(wǎng)關(guān)","安全代理系統(tǒng)"。
在平時(shí)應(yīng)用中,無可避免總要涉及到系統(tǒng)的授權(quán)或安全體系,不管你有無意識的使用Proxy,實(shí)際你已經(jīng)在使用Proxy了。

流程圖

[圖片上傳失敗...(image-844e-1685673732843)]

Num7:裝飾模式

基本概念:裝飾模式(Decorator),動(dòng)態(tài)地給一個(gè)對象添加一些額外的職責(zé),就增加功能來說,裝飾模式比生成子類更為靈活。

UML結(jié)構(gòu)圖

[圖片上傳失敗...(image-f64300-1685673732843)]

上圖是Decorator 模式的結(jié)構(gòu)圖,讓我們可以進(jìn)行更方便的描述:

  • Component是定義一個(gè)對象接口,可以給這些對象動(dòng)態(tài)地添加職責(zé)。

  • ConcreteComponent是定義了一個(gè)具體的對象,也可以給這個(gè)對象添加一些職責(zé)。

Decorator是裝飾抽象類,繼承了Component,從外類來擴(kuò)展Component類的功能,但對于Component來說,是無需知道Decorator存在的。ConcreteDecorator就是具體的裝飾對象,起到給Component添加職責(zé)的功能。

如何使用

假設(shè)情景:某人裝扮自己形象,穿衣服,褲子,鞋子,戴帽子等來把自己給包裝起來,需要把所需的功能按正確的順序串聯(lián)起來進(jìn)行控制,我們應(yīng)該如何設(shè)計(jì)才能做到呢?如下,先看下代碼結(jié)構(gòu)圖:

先創(chuàng)建一個(gè)接口類:Component.java

public interface Component {    
    void show();
}

創(chuàng)建一個(gè)具體的 ConcreteComponent 來實(shí)現(xiàn) Component 接口:Person.java

public class Person implements Component{
    private String name;
    public String getName() {
        return name;
    }

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

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

    @Override
    public void show() {
        System.out.println("裝扮的" + name);
    }
}

創(chuàng)建裝飾類 Decorator 實(shí)現(xiàn) Component 接口

public class Decorator implements Component{
    private Component mComponent;
    public void decoratorObj(Component component){
        mComponent = component;
    }

    @Override
    public void show() {
        if(mComponent != null){
            mComponent.show();
        }
    }
}

分別創(chuàng)建具體的裝飾類:Jeans.java , Pelisse.java, Sandal.java ...等等,分別繼承 Decorator.java 類

/** 牛仔褲 */
public class Jeans extends Decorator {
    @Override
    public void show(){
        System.out.println("穿牛仔褲");
        super.show();
    }

}

客戶端測試類

/**
 * 裝飾模式測試客戶端
 */
public class DecoratorClient {
    public static void main(String[] args) {
        System.out.println("Welcome to Andy.Chen Blog!" +"\n" 
                   +"Decorator Patterns." +"\n");

        Person mPerson = new Person("Andy");

        Sandal mSandal = new Sandal();
        Jeans mJeans = new Jeans();
        TShirt mShirt = new TShirt();

        mShirt.decoratorObj(mPerson);
        mJeans.decoratorObj(mShirt);
        mSandal.decoratorObj(mJeans);
        mSandal.show(); 
    }
}

測試結(jié)果

Welcome to Andy.Chen Blog!
Decorator Patterns.

穿涼鞋
穿牛仔褲
穿T-Shirt
裝扮的Andy

總結(jié)

Decorator模式有以下的優(yōu)缺點(diǎn):

  • 比靜態(tài)繼承更靈活與對象的靜態(tài)繼承相比,Decorator模式提供了更加靈活的向?qū)ο筇砑勇氊?zé)的方式,可以使用添加和分離的方法,用裝飾在運(yùn)行時(shí)刻增加和刪除職責(zé)。使用繼承機(jī)制增加職責(zé)需要?jiǎng)?chuàng)建一個(gè)新的子類,如果需要為原來所有的子類都添加功能的話,每個(gè)子類都需要重寫,增加系統(tǒng)的復(fù)雜度,此外可以為一個(gè)特定的Component類提供多個(gè)Decorator,這種混合匹配是適用繼承很難做到的。

  • 避免在層次結(jié)構(gòu)高層的類有太多的特征,Decorator模式提供了一種“即用即付”的方法來添加職責(zé),他并不試圖在一個(gè)復(fù)雜的可訂制的類中支持所有可預(yù)見的特征,相反可以定義一個(gè)簡單的類,并且用Decorator類給他逐漸的添加功能,從簡單的部件組合出復(fù)雜的功能。

  • Decorator 與它的Component不一樣Decorator是一個(gè)透明的包裝,如果我們從對象標(biāo)識的觀點(diǎn)出發(fā),一個(gè)被裝飾了的組件與這個(gè)組件是有差別的,因此使用裝飾時(shí)不應(yīng)該以來對象標(biāo)識。

  • 產(chǎn)生許多小對象,采用Decorator模式進(jìn)行系統(tǒng)設(shè)計(jì)往往會(huì)產(chǎn)生許多看上去類似的小對象,這些對象僅僅在他們相互連接的方式上有所不同。

參考地址:

1,http://www.cnblogs.com/forlina/archive/2011/06/21/2086114.html

2,http://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html

3,http://blog.csdn.net/cjjky/article/details/7478788

4,http://blog.csdn.net/cjjky/article/details/7384951

5,http://blog.csdn.net/cjjky/article/details/7327200

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容