天天看點

關于建立者模式

神奇的建立者模式可以把産品對象的建立過程移動産品類的外面,可以更細粒度地控制産品對象的建立過程(比如可以選擇傳哪些零件參數,可以控制産品組裝零件的次序等。這與工廠模式不同,工廠模式關心的是拿到最終的産品即可,用戶端不關心艱難的建立過程)。下面先呈上其定義和結構圖。

定義:将一個複雜對象的建構與它的表示分離,使得同樣的建構過程可以建立不同的表示。(這裡的建立與表示分離,指的就是前面說的把産品對象的建立過程移動産品類的外面)

結構圖(來自GoF):

關于建立者模式

Director的作用是根據一定的邏輯,指導産品的建構。直覺上Director是一個上司,而真正幹貨(建構産品)的是Builder。Director還有其他作用,比如隔離Client和Builder,達到一定程度上的解耦,還有就是防止産品在其必要參數未初始化(零件沒組裝)之前,傳回給Client使用。

下面以汽車為産品給出一般形式的代碼實作:

//首先提供一個産品接口,這個是共享給Client的,不然Client怎麼用你的産品
public interface ICar {
    //假設隻給Client提供一個run方法,不同牌子汽車run起來的方式不一樣
    public void run();
    //這個接口對Client隐藏
    protected void add();
}
//實作一輛寶馬,注意權限,這個類是對Client透明的
class BMW implements ICar {
    //為簡化用列印字元串表示寶馬run起來的過程
    private List<String> sequence = new ArrayList<String>();
    protected void add(String s) {
        sequence.add(s);
    }
    public void run() {
        for(String s : sequence) {
            System.out.println(s);
        }
    }
}
//然後是Builder接口
public interface IBuilder {
    //假設建構一輛可以run的車需要三步,啟動引擎(必須)、閃一下燈(可選)、響一下喇叭(可選)
    public void buildEngine();
    public void buildLight();
    public void buildHorn();
    public ICar getCar();
}
//BMW的builder
public class BMWBuilder implements IBuilder {
    private ICar bmw = new BMW();
    public void buildEngine() {
        bmw.add("寶馬的引擎動起來");
    }
    public void buildLight() {
        bmw.add("寶馬的燈亮起來");
    }
    public void buildHorn() {
        bmw.add("寶馬的喇叭響起來");
    }
    public ICar getCar() {
        return this.bmw;
    }
}
//接着是導演類
public class Director {
    //暫時導演隻會生産寶馬
    IBuilder bmwBuilder = new BMWBuilder();
    //建立一輛寶馬的順序
    public BMW getBMW() {
        //先啟動引擎
        bmwBuilder.buildEngine();
        //燈閃一下
        bmwBuilder.buildLight();
        //喇叭太吵,不要了。直接傳回
        return (BMW) bmwBuilder.getCar();
    }
}
//用戶端直接拿到導演,要什麼車吩咐它去生産就可以(隻要它指導怎麼生産)
public class Client {
    public static void main(String[] args) {
        Director director = new Director();
        ICar bmw = director.getBMW();
        bmw.run();
    }
}
           

看到這裡可能會有這樣的疑問,不是說建立與表示分離,用戶端可以更靈活地控制産品的建立過程嗎?但這裡好像都對Client隐藏了啊,這裡的Client隻知道Director,而且建立過程也一無所知!好像真的是這樣。 其實不然,它隻不過是把這個靈活的控制過程放到Director裡面了,而且不同的控制過程用不同的方法給封裝起來了。這種邏輯也是可以接受的,因為具體的Client隻需要關系自己的業務邏輯,不應該過于關注某個對象的建立過程(想想如果Builder裡面有好幾十個builderPart,然後你把Director中getBMW的代碼直接放到Client的情況吧)。是以這裡的控制過程指的是Director裡面,最多Director向Client開放,Client可以自己定制Director。

不過還是有Client直接控制建立過程的情況,它直接省略了Director。這個例子來自《Effective Java》:

//其場景說的是飲料的标簽要顯示食品的營養成分
public class NutritionFacts {
    private final int servingSize;//飲料容量,必須顯示
    private final int servings;//表示每多少容量含多少下面的成分,必須顯示
    private final int calories;//卡路裡,可選顯示
    private final int fat;//脂肪,可選顯示
    private final int sodium;//鈉,可選顯示
    private final int carbohydrate;//碳水化合物,可選顯示

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values
        private int calories = ;
        private int fat = ;
        private int carbohydrate = ;
        private int sodium = ;
        //用構造器很精巧的限制了必須初始化部分要先初始化
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(, )
                .calories().sodium().carbohydrate().build();
    }
}
           

大師寫的代碼果然讓人折服。首先具體的某個産品建立者以靜态内部類的形式内嵌在産品裡面,這樣用Builder的時候前面要帶上哪個産品。另外修改Builder的構造器,以限制某些必須的參數(零件)傳進來了才能把Builder new出來。而後面可選的參數,用戶端可以随心所欲地進行控制。這是給NutritionFacts提供不同構造器,以及用JavaBean提供各屬性getter/setter所不具備的優點。(當然他這裡隻是想示範建立者模式的好處,沒有考慮到面向接口程式設計的問題。而且GoF裡面也說了,産品不一定需要一個統一的接口:通常情況下,有具體生成器生成的産品,它們的表示相差是如此之大以至于給不同的産品設定公共父類沒有太大意義。是以這點還是要看具體場景)

仔細想想,好像StringBuilder(用于同步的StringBuffer也一樣)的append方法就是這種情況(不用Director)。其中StringBuilder就是Builder(同時它也是用戶端直接引用的産品),要構造的String(或者内部的char[])是真正的産品,而各重載的append方法就是各buildPart,而且每個append之後都直接傳回一個産品(StringBuilder)給用戶端。

以上是學習過程中對單例的了解,有問題的地方請不吝指教。

支援原創,轉載請注明出處!