一個設計模式解決一類問題,最近學習了一下建造者模式,看了很多博客,講的模棱兩可,所以決定寫一下我覺得比較好理解的簡介
參考自知乎 https://zhuanlan.zhihu.com/p/58093669,
一、介紹
1、啥是建造者模式
是將一個復雜的對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示
看不懂對吧!其實我也看不懂,不管它,通過案例和代碼加深理解
2、使用場景
一個設計模式解決一類問題,那么建造者模式解決了什么問題呢?——對象的構建過於復雜的問題
- 當一個類的構造函數參數過多(超過四個),並且有的參數可有可無,或者很多產品有默認值。
- 產品類非常復雜或者產品類因為調用順序不同而產生不同作用
3、優點
- 復雜產品的創建步驟分解在不同的方法中,這些方法可以調用順序不同,結果不同,創建結果很清晰
4、缺點
- 如果產品的內部變化復雜,可能會導致需要定義很多具體建造者來實現這種變化。
二、案例
理論總是難以理解的,現在通過案例分析問題,一步步了解使用建造者模式的好處
【案例】好好看一下這個案例
KFC套餐
假如目前KFC里面有很多個套餐
在套餐里面有必點,也有選點,然后每個單品又有大小之分
必點:漢堡(hamburger),薯條(chips)
選點:雞腿(chicken),可樂(cola),披薩(pizza)
【用Java代碼模擬場景】
我們如何構成這么多套餐實例呢?
我們不使用建造者模式也能構建代碼,但是建造者模式會讓代碼看上去更裝逼,代碼到后期更結構化更容易維護和拓展
首先構建這個實體類KFC
public class KFC {
//套餐必點
private String hamburger;
private String chips;
//套餐選點
private String chicken;
private String cola;
private String pizza;
}
我們的想法是不是折疊構造函數來創建實例,下面來嘗試一下
public class KFC{
//省略了上面的屬性.....
//必點套餐A
public KFC(String hamburger,String chips){
this(hamburger,chips,null,null,null);
}
A
//套餐B
public KFC(String hamburger,String chips,String chicken){
this(hamburger,chips,chicken,null,null);
}
//套餐C
public KFC(String hamburger,String chips,String chicken,String cola){
this(hamburger,chips,chicken,cola,null);
}
//......還有好多種組合方式,你會發現使用折疊構造函數的方法十分復雜
//全選
public KFC(String hamburger,String chips,String chicken,String cola,String pizza){
this.hamburger = hamburger;
this.chips = chips;
this.chicken = chicken;
this.cola = cola;
this.pizza = pizza;
}
}
我們會發現使用折疊構造函數的方式很復雜,很惡心,代碼看都不想看
那么有人會想,我可以使用set
方法來創建,我只要一個必點構造就好了,那繼續模擬咯
public class KFC{
//.....省略了屬性
//必點
public KFC(String hamburger,String chips){
this.hamburger = hamburger;
this.chips = chips;
}
//set方法
public void setChicken(String chicken) {
this.chicken = chicken;
}
public void setCola(String cola) {
this.cola = cola;
}
public void setPizza(String pizza) {
this.pizza = pizza;
}
//實例化對象,你會發現這種方式就友好很多
public static void main(String[] args) {
KFC kfc = new KFC("大漢堡","大薯條");
//加小份可樂
kfc.setCola("小可樂");
//加個雞腿
kfc.setChicken("大雞腿");
System.out.println(kfc);
}
}
你會發現使用set
方式就友好了很多
這個雖然友好了很多,但是也有點小毛病,就是你set太隨意了,我可能這個套餐里面沒有這個單品,而使用set的人卻不知道,造成錯誤的套餐出現!。
為了解決上面的兩種問題:一種設計模式解決一類問題,所以建造者模式就出現了
三、建造者模式
先了解一下又哪些角色吧,看不懂沒關系,看一下代碼就懂了
四個角色
Product(產品角色): 一個具體的產品對象。
Builder(抽象建造者): 創建一個Product對象的各個部件指定的抽象接口。
ConcreteBuilder(具體建造者): 實現抽象接口,構建和裝配各個部件。
Director(指揮者): 構建一個使用Builder接口的對象。它主要是用於創建一個復雜的對象。它主要有兩個作用,一是:隔離了客戶與對象的生產過程,二是:負責控制產品對象的生產過程。
拿其中兩個套餐舉例
套餐A:漢堡,薯條,大雞腿
套餐B:漢堡,薯條,小雞腿,小可樂,小披薩其中薯條和漢堡可大可小,並且必須有,
其它的都為固定大小,但是你可以選擇有或沒有
- 產品(KFC)
public class KFC {
//套餐必點
private String hamburger;
private String chips;
//套餐選點
private String chicken;
private String cola;
private String pizza;
//必點
public KFC(String hamburger,String chips){
this.hamburger = hamburger;
this.chips = chips;
}
//set方法
public void setChicken(String chicken) {
this.chicken = chicken;
}
public void setCola(String cola) {
this.cola = cola;
}
public void setPizza(String pizza) {
this.pizza = pizza;
}
}
- Builder
定義一個接口,表明需要建造什么,得到什么
public interface Builder {
void setChicken();
void setCola();
void setPizza();
KFC getKFC();
}
- ConcreteBuilder:
此時應該注意,這個時候還沒有生產套餐,只是定義套餐
套餐A
public class ConcreteBuilder1 implements Builder {
private KFC kfc;
//這一步非常重要
public ConcreteBuilder1(String hamburger,String chips){
kfc = new KFC(hamburger,chips);
}
@Override
public void setChicken() {
kfc.setChicken("大雞腿");
}
@Override
public void setCola() {
kfc.setCola(null);
System.out.println("套餐A里面沒有可樂");
}
@Override
public void setPizza() {
kfc.setPizza(null);
System.out.println("套餐A里面沒有披薩");
}
@Override
public KFC getKFC() {
return kfc;
}
}
套餐B
public class ConcreteBuilder2 implements Builder {
private KFC kfc;
//這一步非常重要
public ConcreteBuilder2(String hamburger,String chips){
kfc = new KFC(hamburger,chips);
}
@Override
public void setChicken() {
kfc.setChicken("小雞腿");
}
@Override
public void setCola() {
kfc.setCola("小可樂");
}
@Override
public void setPizza() {
kfc.setPizza("小披薩");
}
@Override
public KFC getKFC() {
return kfc;
}
}
Director:
真正的執行者,這里把他當作服務員,此時你像服務員點餐
public class Director {
public KFC build(Builder builder){
//套餐里面我只選了雞腿和可樂
builder.setChicken();
builder.setCola();
return builder.getKFC();
}
}
測試
public class BuilderTest {
public static void main(String[] args) {
//套餐A
System.out.println("======套餐A======");
Builder concreteBuilder1 = new ConcreteBuilder1("大漢堡", "小薯條");
KFC kfc1 = new Director().build(concreteBuilder1);
System.out.println(kfc1);
//套餐B
System.out.println("======套餐B======");
Builder concreteBuilder2 = new ConcreteBuilder2("小漢堡", "小薯條");
KFC kfc2 = new Director().build(concreteBuilder2);
System.out.println(kfc2);
}
}
輸出
到了這里你還是會覺得有點麻煩,你會發現,單品可有可無的選擇上面你十分的被動,代碼看上去也很怪,如果你下次想全部單品先選上,再去選套餐的時候,你又要新建一個新的指導者。
我覺得普通的建造者模式不適合參數的可有可無的選擇,普通的建造者模式更側重調控次序,在有些情況下需要簡化系統結構
四、簡化版的建造者模式
這個時候簡化版的建造者模式站出來了
采用鏈式編程的方式
這種模式更加靈活,更加符合定義
既然Director是變化的,並且其實在生活中我們自己本身就是Director,所以這個時候我們可以把Director這個角色去掉,因為我們自身就是指導者
- 產品(product)
public class KFC {
//套餐必點
private String hamburger;
private String chips;
//套餐選點
private String chicken;
private String cola;
private String pizza;
public KFC(String hamburger,String chips){
this.hamburger = hamburger;
this.hamburger = chips;
}
public void setChicken(String chicken) {
this.chicken = chicken;
}
public void setCola(String cola) {
this.cola = cola;
}
public void setPizza(String pizza) {
this.pizza = pizza;
}
}
- 抽象建造者(builder)
public abstract class Builder {
abstract Builder setChicken();
abstract Builder setCola();
abstract Builder setPizza();
abstract KFC getKFC();
}
- 具體建造者(ConcreteBuilder)
public class ConcreteBuilder extends Builder {
KFC kfc;
public ConcreteBuilder(String hamburger,String chips){
kfc = new KFC(hamburger,chips);
}
@Override
Builder setChicken() {
kfc.setChicken("雞腿");
return this;
}
@Override
Builder setCola() {
kfc.setCola("可樂");
return this;
}
@Override
Builder setPizza() {
kfc.setPizza("披薩");
return this;
}
@Override
KFC getKFC() {
return kfc;
}
}
- 測試
public class BTest {
public static void main(String[] args) {
KFC kfc = new ConcreteBuilder("漢堡","薯條").setChicken().setCola().getKFC();
}
}
如果不需要抽象建造者的角色來規定生產內容,那么代碼到這里其實還有進一步的簡化空間。
【關鍵代碼】
使用靜態內部類的方式
【進一步簡化】
public class KFC {
//套餐必點
private String hamburger;
private String chips;
//套餐選點
private String chicken;
private String cola;
private String pizza;
//一定要有一個帶有Builder參數的建造者
private KFC(Builder builder) {
this.hamburger = builder.hamburger;
this.chips = builder.chips;
this.chicken = builder.chicken;
this.cola = builder.cola;
this.pizza = builder.pizza;
}
//注意必須為靜態內部類
public static class Builder{
//套餐必點
private String hamburger;
private String chips;
//套餐選點
private String chicken;
private String cola;
private String pizza;
public Builder(String hamburger,String chips){
this.hamburger = hamburger;
this.chips = chips;
}
public Builder setChicken(){
this.chicken = "小雞腿";
return this;
}
public Builder setCola(){
this.cola = "小可樂";
return this;
}
public Builder setPizza(){
this.pizza = "小披薩";
return this;
}
//生成一個產品
public KFC getKFC(){
return new KFC(this);
}
}
}
測試
public class BuilderTest {
public static void main(String[] args) {
KFC kfc = new KFC.Builder("大漢堡", "小薯條").setChicken().setCola().getKFC();
System.out.println(kfc);
}
}
五、建造者模式和抽象工廠模式的區別
通過上面的代碼,你發現普通的建造者模式和抽象工廠模式真的很像,在建造者模式中的builder角色很像超級工廠,然后contracterBuilder很像具體的工廠,都是規定了建造的內容
那么它們之前 有什么區別呢
- 建造者模式有指導者這個角色,直接返回一個組裝好的產品,而抽象工廠模式返回一系列相關的產品,這些產品位於不同的產品等級結構,構成了一個產品族
- 建造者模式更適合復雜的產品構建
- 可以將抽象工廠模式理解成汽車零件生產工廠,而建造者模式看出組裝工廠
【總結】
學習一類技巧是為了解決一類問題,學習設計模式主要是為了理解它的思想,將來遇到代碼的編寫,使用這種模式會更加的方式,而沒有必要刻意的去使用,但是需要刻意的去練習,形成這種思想