文章出處

  我們還是舉上一節的例子:生產汽車。上一節我們通過模板方法模式控制汽車跑起來的動作,那么需求是無止境的,現在如果老板又增加了額外的需求:汽車啟動、停止、鳴笛引擎聲都由客戶自己控制,他想要什么順序就什么順序,那該如何做呢?

1. 汽車無休止的改造

        假如現在要生產兩種車,奔馳和寶馬,這兩輛車都有共性,我們所需要關注的是單個車的運行過程,這才是老板所關心的點所在。我們先這樣想,針對這個需求,我們要找到一個切入點,那就是產品類,每個車都是一個產品,那么在產品類中我們可以控制車的運行順序,這樣每個車都可以擁有自己想要的順序了。基于此,我們設計如下類圖:

         我們看到CarModel中有個setSequence方法,通過傳入一個ArrayList來控制運行順序,run方法根據這個ArrayList中保存的順序執行,然后奔馳車和寶馬車分別繼承這個CarModel即可,這貌似是很好的實現,它很像上一節的模板方法模式,只是多了個方法可以設置運行順序。我們看一下CarModel具體代碼的實現:

public abstract class CarModel {  
      
    private ArrayList<String> sequence = new ArrayList<String>(); //維護一個ArrayList保存執行命令關鍵字  
      
    protected abstract void start();  
    protected abstract void stop();  
    protected abstract void alarm();  
    protected abstract void engineBoom();  
      
    final public void run() {  
        for(int i = 0; i < this.sequence.size(); i ++) { //根據ArrayList中保存的順序執行相應的動作  
            String actionName = this.sequence.get(i);  
            if(actionName.equalsIgnoreCase("start")) {  
                this.start(); //啟動汽車  
            } else if(actionName.equalsIgnoreCase("stop")) {  
                this.stop(); //停止汽車  
            } else if(actionName.equalsIgnoreCase("alarm")) {  
                this.alarm(); //汽車鳴笛  
            } else if(actionName.equalsIgnoreCase("engine boom")) {  
                this.engineBoom(); //汽車轟鳴  
            }  
        }  
    }  
      
    final public void setSequence(ArrayList<String> sequence) { //獲得執行順序的命令,即一個ArrayList  
        this.sequence = sequence;  
    }  
}  

 CarModel中的setSequence方法允許客戶自己設置一個順序,我們看看子類的實現:

public class BenzModel extends CarModel {  
  
    @Override  
    protected void start() {  
        System.out.println("奔馳啟動……");  
    }  
  
    @Override  
    protected void stop() {  
        System.out.println("奔馳停止……");  
    }  
  
    @Override  
    protected void alarm() {  
        System.out.println("奔馳鳴笛……");  
    }  
  
    @Override  
    protected void engineBoom() {  
        System.out.println("奔馳轟鳴");  
    }  
  
}  
//寶馬就略了……一樣的  

   下面我們增加一個測試類實現該需求:

public class Client {  
  
    public static void main(String[] args) {  
        BenzModel benz = new BenzModel();  
        //存放run順序  
        ArrayList<String> sequence = new ArrayList<String>();  
        sequence.add("engine boom"); //老板說:跑之前先轟鳴比較帥!  
        sequence.add("start");  
        sequence.add("stop");  
        //我們把這個順序賦予奔馳  
        benz.setSequence(sequence);  
        benz.run();  
    }  
}  

 

  這樣好像已經順利完成了任務了,但是別忘了,我們這只是滿足了一個需求,如果下一個需求是寶馬車只轟鳴,再下一個需求是奔馳車只跑不停……等等……那豈不是要一個個寫測試類來實現?顯然這不是我們想要的。

        我們可以這樣做:為每種產品模型定義一個建造者,你要啥順序直接告訴建造者,由建造者來建造即可,于是我們重新設計類圖:

        我們增加了一個CarBuilder類,由它來組裝各個車模型,要什么類型的順序就由相關的子類去完成即可,我們來看看CarBuilder的代碼:

public abstract class CarBuilder {  
    //建造一個模型,你要給我一個順序要求  
    public abstract void setSequence(ArrayList<String> sequence);  
    //設置完畢順序后,就可以直接拿到這個車輛模型了  
    public abstract CarModel getCarModel();  
}  

    很簡單,每個車輛模型都要有確定的運行順序,然后才能返回一個車輛模型,奔馳車和寶馬車組裝者的代碼如下:

public class BenzBuilder extends CarBuilder {  
  
    private BenzModel benz = new BenzModel(); //奔馳車模型  
      
    @Override  
    public void setSequence(ArrayList<String> sequence) {  
        this.benz.setSequence(sequence); //設置奔馳車模型的運行順序  
    }  
  
    @Override  
    public CarModel getCarModel() {  
        return this.benz; //將這個模型返回  
    }  
}  
//寶馬車一樣,不寫了……  

現在兩輛車的組裝者都寫好了,現在我們寫一個測試類來測試一下:

public class Client {  
  
    public static void main(String[] args) {  
  
        //存放run順序  
        ArrayList<String> sequence = new ArrayList<String>();  
        sequence.add("engine boom");  
        sequence.add("start");  
        sequence.add("stop");  
        //要用這個順序造一輛奔馳  
        BenzBuilder benzBuilder = new BenzBuilder();  
        //把順序給奔馳組裝者  
        benzBuilder.setSequence(sequence);  
        //奔馳組裝者拿到順序后就給你生產一輛來  
        BenzModel benz = (BenzModel) benzBuilder.getCarModel();  
        benz.run();  
    }  
  
}  

 如果我要生產一輛寶馬車,只需要換成寶馬車的組裝者即可,這樣我們不用直接訪問產品類了,全部訪問組裝者就行,是不是感覺到很方便,我管你怎么生產,我扔給你個順序,你給我弄輛車出來,要的就是這種效果!

 

        可是人的需求是個無底洞,特別是老板,他哪天不爽了,又要換順序,這樣還是挺麻煩的,四個過程(start、stop、alarm、engine boom)按排列組合也有很多中情況,我們不能保證老板想要哪種順序,咋整?無奈,我們只能使出最后的殺手锏了,找個設計師過來指揮各個時間的先后順序,然后為每種順序指定一個代碼,你說一種我們立刻就給你生產!我們再修改一下類圖……

        類圖看著有點復雜,其實不然,只是在原來的基礎上增加了一個Director類充當著設計師的角色,負責按照指定的順序生產模型,比如我們要一個A順序的奔馳車,B順序的奔馳車,A順序的寶馬車,B順序的寶馬車……等等,我們來看下Director類的代碼:

public class Director {  
    private ArrayList<String> sequence = new ArrayList<String>();  
    private BenzBuilder benzBuilder = new BenzBuilder();  
    private BWMBuilder bwmBuilder = new BWMBuilder();  
      
    //A順序的奔馳車  
    public BenzModel getABenzModel() {  
        this.sequence.clear();  
        this.sequence.add("start");  
        this.sequence.add("stop");  
        //返回A順序的奔馳車  
        this.benzBuilder.setSequence(sequence);  
        return (BenzModel) this.benzBuilder.getCarModel();  
    }  
      
    //B順序的奔馳車  
    public BenzModel getBBenzModel() {  
        this.sequence.clear();  
        this.sequence.add("engine boom");  
        this.sequence.add("start");  
        this.sequence.add("stop");  
        //返回B順序的奔馳車  
        this.benzBuilder.setSequence(sequence);  
        return (BenzModel) this.benzBuilder.getCarModel();  
    }  
      
    //C順序的寶馬車  
    public BenzModel getCBWMModel() {  
        this.sequence.clear();  
        this.sequence.add("start");  
        this.sequence.add("alarm");  
        this.sequence.add("stop");  
        //返回C順序的寶馬車  
        this.bwmBuilder.setSequence(sequence);  
        return (BenzModel) this.bwmBuilder.getCarModel();  
    }  
  
    //D順序的寶馬車  
    public BenzModel getDBWMModel() {  
        this.sequence.clear();  
        this.sequence.add("engine boom");  
        this.sequence.add("start");  
        //返回D順序的寶馬車  
        this.bwmBuilder.setSequence(sequence);  
        return (BenzModel) this.bwmBuilder.getCarModel();  
    }  
      
    //還有很多其他需求,設計師嘛,想啥需求就給你弄啥需求  
}  

    有了這樣一個設計師,我們的測試類就更容易處理了,比如現在老板要10000輛A類奔馳車,100000輛B類奔馳車,20000C類型寶馬車,D類型不要:

public class Client {  
  
    public static void main(String[] args) {  
  
        Director director = new Director();  
          
        for(int i = 0; i < 10000; i ++) {  
            director.getABenzModel();  
        }  
          
        for(int i = 0; i < 100000; i ++) {  
            director.getBBenzModel();  
        }  
          
        for(int i = 0; i < 20000; i ++) {  
            director.getCBWMModel();  
        }  
    }  
  
}  

  是不是很清晰很簡單,我們重構代碼的最終第就是簡單清晰。這就是建造者模式。

 

2. 建造者模式的定義

        我們來看看建造者模式的一般定義:Separate the construction of a complex object from its representation so that the same construction process can create different representations. 即:將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。比如上面的例子,我們可以用同樣的順序構造不同的車。建造者模式的通用類圖如下:

        Product是最終的產品類,Builder是建造者,Director是指揮者。Director負責安排已有模塊的順序,然后告訴Builder開始建造。

3. 建造者模式的優點

        1)封裝性:使用建造者模式可以是客戶端不必知道產品內部組成的細節。

        2)建造者獨立,容易擴展:BenzBuilder和BMWBuilder是相互獨立的,對系統擴展非常有利。

        3)便于控制細節風險:由于具體的建造者是獨立的,因此可以對建造者過程逐步細化,而不對其他的模塊產生任何影響。

4. 建造者模式的使用場景

        1)相同的方法,不同的執行順序,產生不同的事件結果時,可以使用建造者模式。

        2)多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不想同時,可以使用建造者模式。

        3)產品類非常復雜,或者產品類中的調用順序不同產生了不同的效能,這時候可以使用建造者模式。

        4)在對象創建過程中會使用到系統的一些其他對象,這些對象在產品對象的創建過程中不易得到,也可以采用建造者模式封裝該對象的創建過程。這種場景只能是一個補償的方法,因為一個對象不容易獲得,而在設計階段竟然沒有發現,而要通過設計這模式來柔化創建過程,本身設計已經出問題了。

        到這里,我們會發現,建造者模式和工廠方法模式有點像。但是兩者有區別:建造者模式關注的是零件類型和裝配工藝(順序),而工廠模式是創建一個對象,這是最大不同的地方。

        創建者模式就介紹這么多吧,如有錯誤之處,歡迎留言指正~


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()