設計模式之建築者(builder)

一 目的:

    把一個複雜對象的創建過程和其展現過程分離開來,從而使相同的創建過程能夠創造不同的展現。

二 動機:

    一個閱讀器要求能夠將富文本格式的文檔轉換成很多文本格式。閱讀器可能將富文本文檔轉換成普通的Ascii 文本,或者轉換成可以編輯和交互的文本窗口。這裏存在一個問題,這種可能的轉換數量是開放的。所以要求我們在增加一種新的轉換方式的時候儘量簡單,不能夠去修改閱讀器。

    其中有一個解決方案是在這個閱讀器(RTFReader)上配置一個能夠將富文本格式轉換成其他的文本的文本轉換器(TextConverter)。當閱讀器在解析這個富文檔的時候,它調用文本轉換器去轉換。當閱讀器識別出富文本的種類後,它分別去調用文本轉換器的不同接口去進行處理。文本轉換器對象負責數據轉換和將內容以特定的形式展現出來。

     文本轉換器的子類指定了不同的轉換方法和展現格式。例如:一個 ASCIIConverter 忽略除普通文字的其他任何格式。一個TexConverter,相反,爲了創建一個Tex展現格式,能夠獲取所有格式信息的,會實現所有請求的接口。一個TextWidgetConverter 會創建一個複雜的用戶接口,從而使用戶能夠看到和編輯這些文本。

    每個轉換器都會有自己創建和裝配複雜對象的機制,並且把自己放在接口的後面。轉換器和負責解析富文本文檔的閱讀器進行分離。

    建築者模式考慮了所有的這些關係。在整個模式中,每種轉換器被稱作建築者。閱讀器被稱爲主管。在這個例子中,建築者模式把解析文本形式的算法和轉換後的文本的創建和展示分離開來。這個可以使我們能夠重複使用閱讀器解析富文本文檔的算法,從而使我們只要把閱讀器配置成不同的轉換器,就能夠創建不同的文本展現。

   三  應用:

    我們可以在以下場景中使用建築者模式:

    1 創建複雜對象的算法 和 組成對象的局部以及他們怎麼裝配的過程 需要保持獨立的情況;

    2 被創建的對象的創建過程必須允許不同形式的展現。

  四   結構:

  

  五 參與者:

    . 建築者(TextConverter)

         聲明瞭創建產品的不同部分的各種接口。

     . 具體創建者(ASCIIConverter,TexConverter,TextWidgetConverter)

         通過實現接口來創建和裝配產品的部分。

         定義和跟蹤他創建對象的展現。

         提供一個獲取他創建的產品的接口。

    . 主管 (Director)

        通過建築者的接口創建一個對象。

     . 產品

        展示被創建的對象。建築者創建產品的內部展現並且定義了他裝配的過程。

        包括構成這個產品的局部的類,並且包括用來完成裝配這個產品的局部的接口 。

   六  合作

       用戶代碼創建主管對象,並且用設計好的建築者配置好。

       當產品的部分需要被創建的時候,主管通知建築者。 

       建築者響應主管的請求,並且把部分添加到產品中。

        用戶代碼從建築者中獲取到產品。

       下面是時序圖:

        

七 影響:
    下面列舉了建築者模式的主要影響
    1 建築者模式使你可以改變產品的內部展現。建築者給主管提供了一個創建產品的抽象接口。接口使得建築者能夠隱藏產品的展現和內部結構。它同時也隱藏了這個產品是如何裝配的。因爲這個產品是通過接口創建的。如果你想改變一個產品的內容展現,你必須做的是定義一個新的建築者。

    2 建築者模式隔離了創建過程和展示。建築者模式通過封裝複雜對象的創建方式提高了模塊化程度。客戶代碼不需要知道定義產品內部結構的類的任何東西。這些類不會出現在建築者的接口中。每個具體的建築者包含了創建和裝配某個具體類型的產品的所有代碼。這些代碼只寫一次,主管可以重複使用他們來創建相同一組的產品變種。在早期的富文本格式例子中,我們其實還可以定義其他的閱讀器,比如說標準通用標示語言閱讀器,然後用同樣的文本轉換器(TextConverter)來產生標準通用標示語言的展現形式,比如ASCIIText,TexText,以及TextWidget。

    3 它使你能夠在創建的過程中更好的 控制。不像其他的創建模式一樣,一次性創建產品,建築者模式在主管的控制下一步一步的創建產品。只有當這些產品全部創建好了,主管才能夠通過建築者來獲取到這個產品。因此,建築者模式的接口比其他創建模式更多的反映了創建過程。這個給你在創建產品和決定最終結果的內部結構更多的控制。

八 實現

    有一個抽象的建築者類來定義創建主管請求創建的組件的操作。這些操作默認什麼事情也不做。一個具體的建築者類會爲自己感興趣的組件去重寫這些操作。

    這裏還有其他一些事項問題需要考慮:

    1 裝配和創建的接口。創建者一步一步的創建這些產品。因此,這個創建者的接口必須足夠,覆蓋所有的具體實現類的操作。

   在設計中,需要考慮一個關鍵的問題就是創建和裝配的模型。把創建請求的結果簡單的追加到產品的模型通常是有效的。在富文本例子中,建築者轉換和追加了下一個內容到之前他已經創建的產品上。但是有些時候,他也許需要訪問早起創建的部分產品。。在迷宮的例子中,我們展現的例子代碼,迷宮建築者的接口讓你在存在的房間中增加一扇門。樹形結構,比如從下到上創建的分析樹是另一個例子。在那種情況下,建築者會返回子節點給主管。主管然後把他們又傳給建築者來創建復節點。

   2 爲什麼沒有產品的抽象類?在一般的情況下,被具體創建者創建的產品在展現時非常不同,所以給不同的產品一個共同的接口得不償失。在符文本文檔中,ASCIIText 和TextWidget 對象沒有共同的接口,所以他們不需要。因爲客戶端代碼總是給主管配置合適的具體創建者,用戶可以知道哪個具體的創建者在被使用,從而能夠相應的處理它的產品。

   3 在建築者的接口方法默認爲空。在C++中,創建方法設計成不聲明爲虛函數。他們被定義爲空函數,從而使客戶重載他們感興趣的操作。

九 例子代碼

    我們會定義一個CreateMaze  成員函數的變種。它的一個參數是 類 MazeBuilder。

    這個MazeBuilder類定義了以下接口:

package com.hermeslch.pattern;
public interface MazeBuilder {
	public void BuildMaze();
	public void BuildRoom(int room);
	public void BuildDoor(int roomFrom,int roomTo);
	public  Maze GetMaze();
	
}

    這個接口能夠創建三件東西:(1)迷宮 (2)有一個數字標識的房間 (3)被標識的房間之間的門。GetMaze操作把迷宮返回給客戶端代碼。MazeBuilder的子類會重寫這些操作,從而能夠返回他們創建的迷宮。

    所有的創建迷宮的操作默認不做任何事情。

   把MazeBuilder做爲參數,我們能夠改變CreateMaze成員函數,把MazeBuilder作爲一個參數。

	public Maze CreateMaze(MazeBuilder builder){
		builder.BuildMaze();
		builder.BuildRoom(1);
		builder.BuildRoom(2);
		builder.BuildDoor(1,2);
		return builder.GetMaze();
	}

    和原來CreateMaze的版本進行比較,注意到建築者模式隱藏了迷宮的內部展現,即定義房間,門和牆的類,以及這些部分怎麼組裝成最終的迷宮。有些人會注意到房間和門是有定義的類,但是沒有牆的定義。這樣使得我們很容易改變這個迷宮的展現形式,因爲MazeBuilder 的客戶端代碼都沒有改變。

    像其他的創建模式一樣,建築者模式通過定義的接口隱藏了對象是怎樣創建的。這就意味着我們可以重複使用MazeBuilder來創建不同的迷宮。CreateComplexMaze操作給了一個很好的例子:

public Maze CreateComplexMaze(MazeBuilder builder){
		builder.BuildMaze();
		builder.BuildRoom(1);
                //...
                builder.BuildRoom(100);
		return builder.GetMaze();
	}

注意到MazeBuilder自己不創建迷宮,他的目的只是定義一個創建迷宮的接口。爲了方遍,它事先創建一個空的實現。MazeBuilder的子類來做實際的工作。

子類StandarMazeBuilder是一個創建簡單迷宮的實現。他用 成員變量 _currentMaze記錄了他創建的迷宮。CommonWall是一個決定普通的兩個房間的牆壁方向的通用的操作。

這個 StanderMazeBuilder 的構造器簡單的初始化_currentMaze.

public StandarMazeBuilder(){
		_currentMaze = null;
	}

BuildMaze 初始化一個迷宮,其他的操作來組裝,把最終結果返回給客戶端。

    public void buildMaze(){
        _currentMaze = new Maze();

    }
public Maze getMaze(){
        return _currentMaze;
    }

buildRoom 操作創建一個房間,並且創建它周圍的牆壁。

    public void buildRoom(int n){
        if(_currentMaze.roomNo(n) != null){
            Room room = new Room(n);
            _currentMaze.AddRoom(room);
            room.setSide(Direction.North, new Wall());
            room.setSide(Direction.South, new Wall());
            room.setSide(Direction.East, new Wall());
            room.setSide(Direction.West, new Wall());
        }
    }

爲了創建兩個房間中的門,StanderMazeBuilder尋找迷宮裏面的兩個房間,然後找到他們臨近的牆壁。

public void buildDoor(int n1,int n2){
        Room r1 = _currentMaze.roomNo(n1);
        Room r2 = _currentMaze.roomNo(n2);
        Door d = new Door(r1,r2);
        r1.setSide(commonWall(r1,r2), d);
        r2.setSide(commonWall(r2,r1), d);
    }

客戶端代碼現在能夠使用CreateMaze,結合StanderMazeBuiler來創建迷宮。

	@Test
	public void CreateMazeTestBuilder(){

		MazeGame game = new MazeGame();
		StandarMazeBuilder smb = new StandarMazeBuilder();
		game.CreateMaze(smb);
		Maze maze  = smb.getMaze();
		
	}

我們本可以把 StandarMazeBuilder的所有操作都放進 Maze,讓Maze自己創建自己。但是把Maze弄小一些,可以使我們更加容易理解和修改,StandarMazeBuilder 很容易從Maze中分離開來。但最重要的是,把這兩者分離開來,能夠使你更有機會有不同版本的MazeBuilder.每個MazeBuilder都能夠創建不同的房間,門和牆壁。

一個更加獨特的MazeBuilder是CountingMazeBuilder。這個builder根本不創建迷宮。它的作用就是計數組成這個迷宮的組件。

package com.hermeslch.pattern;

public class CoutingMazeBuilder implements MazeBuilder {
	public CoutingMazeBuilder(){
		room = 0;
		door = 0;
	}
	@Override
	public void buildMaze() {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void buildRoom(int room) {
		// TODO Auto-generated method stub
		room ++;
	}
	@Override
	public void buildDoor(int roomFrom, int roomTo) {
		// TODO Auto-generated method stub
		door++;
	}

	@Override
	public Maze getMaze() {
		// TODO Auto-generated method stub
		return null;
	}
	
	public int  getCountOfRoom(){
		return room;
	}
	
	public int  getCountOfDoor(){
		return door;
	}
	private int room;
	private int door;
}

這個類的構造函數初始化其內部的計數器,然後通過相應的增加計數器,重寫MazeBuilder的操作函數。

下面是描述一個客戶端代碼怎麼去使用CoutingMazeBuilder的。

MazeGame  game = new MazeGame();

CoutingMazeBuilder builder = new CountingMazeBuilder();

game.CreateMaze(builder);

System.out.println("the Maze has " + builder.getCountofRoom() + "rooms and " + builder.getCountofDoor() + "doors" )

十  相關模式

   抽象工廠模式和建築者模式相同的一點是他們創建的對象都很複雜。最主要的區別在於建築者模式的重點在於它關注複雜對象一步一步的創建。抽象工廠的關注重點在於整個家族產品的創建。建築者模式在最後一步才返回產品對象,而抽象工廠模式關注的是立刻返回產品對象。

    一個複合材料(Composite)經常是用建築者模式來創建的。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章