1. 程式人生 > >一段對話講完建造者模式

一段對話講完建造者模式

截止今天,小秋學習Java剛剛滿三個月。此刻的小秋感覺自己Java學的還不錯,想去帥地面前炫耀一番,於是,就發生了一下一番對話…..

得意的小秋

帥地:嗨,小秋,看你今天氣色不錯啊。最近Java學的怎麼樣了?

小秋:說實話,比起三個月前我學指標的那段日子,這Java太友好了,感覺就是分分種的事(暗自得意)。

帥地:我靠,你這口氣還挺大啊。從c的面向過程到Java的面向物件,還習慣不?

小秋:哎,還行,無非就是“一切皆物件”,Java中的物件感覺類似於C中的結構體。反正不過三七二十一,我腦子裡都把他們當成是一個物件就得了。(內心自我感覺良好)

帥地:看你也學了三個月了,要不我隨便拿道題考考你?(讓你不謙虛,暗自偷笑)

小秋:好啊,正好練練手(嘿嘿,終於可以展現實力了)。

過載多次的建構函式

帥地:假如有一個蛋糕Cake物件,蛋糕這個物件有一個必選屬性size,還有一些可選屬性apple,banana,orange,mango等,必選屬性代表使用者必須要指定蛋糕的大小,可選屬性代表這些蛋糕要加哪些材料。

小秋:這個很簡單啊,建立一個Cake類,裡面有size,apple,banana,orange,mango屬性,然後構造器的引數裡指定size這個引數就可以了。我直接上程式碼吧:

public class Cake {
    private int size;
    private String apple;
    private
String banana; private String orange; private String mango; //new時必須給出size public Cake(int size) { this.size = size; } }

帥地:可選引數呢?我要new一個size=30,並且新增apple的蛋糕怎麼辦?

小秋:哦,我寫的太快,忘了過載了,稍等(心想,這還不簡單)。

public class Cake {
    private int size;
    private String apple;
    private String banana;
    private
String orange; private String mango; public Cake(int size) { this.size = size; } public Cake(int size, String apple) { this.size = size; this.apple = apple; } public Cake(int size, String apple, String orange) { this.size = size; this.apple = apple; this.orange = orange; } public Cake(int size, String apple, String orange, String mango) { this.size = size; this.apple = apple; this.orange = orange; this.mango = mango; } }

小秋:這下總可以了吧,你要加哪些料,你就使用哪個構造器。全部給你過載了。

帥地:(露出狡猾的表情)寫建構函式倒是挺快的,那如果我要只加apple和mango的蛋糕呢?

小秋:啊?好吧,你這是逼我把所有組合的構造器都寫出來。

於是,小秋把所有構造器的組合都寫了出來。由於size是個必須引數,把其他四個可選引數進行組合,一共有16種。

噼裡啪啦,劈里啪啦,小秋一口氣把他們全部寫出來了

public class Cake {
    private int size;
    private String apple;
    private String banana;
    private String orange;
    private String mango;

    public Cake(int size){
        this.size = size;
    }
    public Cake(int size, String apple){
        this.size = size;
        this.apple = apple;
    }
    public Cake(int size, String banana){
        this.size = size;
        this.banana = banana;
    }
    .....
}

小秋:好了,這下,你要啥組合有啥組合了。

帥地:四個可選引數你就寫了這麼一大堆引數了。確定這樣寫?

小秋:我覺得挺好的啊,反正很快,多寫幾個就多寫幾個吧。

帥地:那如果給你6個可選引數呢?

這時候小秋偷偷算了一些,發現一共有74種組合?

小秋:不就是74種組合,我覺得問題不是很大(心有點虛)。

帥地:那萬一有10個可選引數呢?

小秋:…..

帥地:而且你過載那麼多構造器,使用者在在new的時候,第一個引數和第二個引數代表什麼,使用者混亂了怎麼吧?例如,你有個apple+banana的構造器

public Cake(int size, String apple, String banana){
    this.size = size;
    this.apple = apple;
    this.banana = banana;
}

但是使用者在new的時候,可能忘記了引數的順序

Cake cake = new Cake(size,“banana”,“apple”)。

小秋:我會提供相應的文件啊,忘記了可以看文件勒。

帥地:幾百個建構函式,而且還那麼相似,你去看下文件試試,然後說說你的心情。

小秋:……(不知所措)。

通過Javabean的模式

帥地:有沒其他什麼辦法?

小秋:我想到另一種辦法了,我可以通過set和get方法來設定可選引數的。我直接上程式碼你看看

public class Cake {
    private int size;
    private String apple;
    private String banana;
    private String orange;
    private String mango;

    public Cake(int size) {
        this.size = size;
    }
    //通過set來新增材料
    public void setApple(String apple) {
        this.apple = apple;
    }

    public void setBanana(String banana) {
        this.banana = banana;
    }

    public void setMango(String mango) {
        this.mango = mango;
    }

    public void setOrange(String orange) {
        this.orange = orange;
    }
}

此時的小秋有點得意….

帥地:挺不錯,這種方法比剛才的好多了,又簡潔。

此時如果要new一個apple+orange+mango的蛋糕的話,程式碼如下:

Cake cake = new Cake(30);
cake.setApple("apple");
cake.setOrange("orange");
cake.setMange("mange");
引數依賴檢查問題

帥地:這種方法也是有缺點,例如用構造器過載時一行程式碼就可以搞定了,現在要用四行程式碼。

小秋:反正我覺得這樣很nice(得意中…)。

帥地:不過這樣寫有一個致命的缺點,假如那些屬性之間存在依賴性的話,怎麼辦?例如Cake多了A,B兩個屬性,並且這兩個屬性之間存在依賴關係。如果你設定了屬性A,但是沒有設定屬性B,那麼這個Cake物件就會出問題。或者屬性的先後順序設定也可能會導致出現問題。對於這種情況,你在什麼地方檢查這種相互依賴的邏輯?

小秋:有點矇蔽,不知所措….。

小秋:那你說怎麼辦?

靜態內部類

帥地:其實你已經做的相當不錯了,不過我今天就教你另外一個辦法,我們可以開放一個靜態內部類專門用來與外界打交道,用來收集使用者想要設定的屬性並且做檢查。直接上程式碼:

public class Cake {
    private int size;
    private String apple;
    private String banana;
    private String orange;
    private String mango;
    //private,讓外面無法直接建立
    private Cake(Builer builer) {
        this.size = builer.size;
        this.apple = builer.apple;
        .....
    }
    //專門用來與外界打交道
    public static class Builer {
        private int size;
        private String apple;
        private String banana;
        private String orange;
        private String mango;
        public void setSize(int size) {
            this.size = size;
        }
        //為了省點程式碼,其他的省略
        public Cake build() {
            //檢查引數之間的依賴關係是否正確
            return new Cake(this);
        }
        .....
    }
}

假如我要new一個apple+orange的Cake

Cake.Builer builer = new Cake.Builer();
builer.setSize(30);
builer.setApple("apple");
builer.setOrange("orange");
//建立一個蛋糕
Cake cake = builer.build();

帥地:這種方法牛吧?這還不夠,我們還可以採用鏈式呼叫的方法。

鏈式呼叫
public class Cake {
    private int size;
    private String apple;
    private String banana;
    private String orange;
    private String mango;
    //private,讓外面無法直接建立
    private Cake(Builer builer) {
        this.size = builer.size;
        this.apple = builer.apple;
        .....
    }
    //專門用來與外界打交道
    public static class Builer {
        private int size;
        private String apple;
        private String banana;
        private String orange;
        private String mango;
        //返回引數改為Builer
        public Builer setSize(int size) {
            this.size = size;
            return this;
        }

        public Builer setApple(String apple) {
            this.apple = apple;
            return this;
        }
        //為了省點程式碼,其他的省略
        public Cake build() {
            //檢查引數之間的依賴關係是否正確
            return new Cake(this);
        }
    }
}

如何使用?

Cake cake = new Cake.Builer()
            .setSize(30)
            .setApple("apple")
            .setOrange("orange")
            .build();

一行程式碼就搞定了。

帥地:厲害吧?

小秋:漲知識了,看來我還是太年輕了,以後得好好向帥地學習。

建造者模式

帥地:其實,上面那種方法算是23種設計模式中的其中一種—-建造者模式。不過這只是一個簡化版的建造者模式。

對於建造者模式,具體的UML圖是這樣的:

在這個UML圖中,Builder是一個介面,定義一套規範,而我們使用的例子中,沒有使用介面,直接使用具體類,但核心思想還是一樣的。

其核心思想就是:將一個複雜的物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

小秋:哇,強啊。我要給你點贊…..

算是第二次採取對話的方式寫….,以後會多采取這種方式來寫勒。

關注公我的眾號:苦逼的碼農,獲取更多原創文章,後臺回覆禮包送你一份時下熱門的資源大禮包。同時也感謝把文章介紹給更多需要的人