從零開始學設計模式(四)—建造者模式(Builder Pattern)
建造者模式(Builder Pattern)
建造者模式使用多個簡單的物件一步一步構建成一個複雜的物件,這種型別的設計模式也屬於建立型模式,它提供了一種建立物件的最佳方式。
一個Builder 類會一步一步構造最終的物件。該Builder 類是獨立於其他物件的
難度系統: 中級
提出者: Gang Of Four
意圖
將複雜物件的構造與其表示分離,以便相同的構造過程可以建立不同的表示
主要解決:主要解決在軟體系統中,有時候面臨著"一個複雜物件"的建立工作,其通常由各個部分的子物件用一定的演算法構成;由於需求的變化,這個複雜物件的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的演算法卻相對穩定。
何時使用:一些基本部件不會變,而其組合經常變化的時候。
如何解決:將變與不變分離開。
關鍵程式碼:建造者:建立和提供例項,導演:管理建造出來的例項的依賴關係。
解釋
現實世界的例子
想象一個角色扮演遊戲的角色生成器。最簡單的選擇是讓電腦為你建立角色。但是如果你想選擇角色細節,如職業、性別、髮色等。當你所有選擇一步一步都選定好時,角色生成器也就逐步生成了一個角色,這一過程就是建造者模式建立物件的過程
簡而言之
允許你建立不同風格的物件,同時避免建構函式汙染。當一個物件可能有多種風格時很有用。或者當建立物件涉及很多步驟時
維基百科說
The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern(建造者模式是一種物件建立軟體設計模式,旨在找到伸縮構造器反模式的解決方案)
說到這裡,讓我補充一下伸縮建構函式反模式是什麼。在這一點或其他方面,我們可能都見過類似下面這樣的建構函式
public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { }
正如你可以看到的未來那樣,這個建構函式引數的數量會很快失控,並且很難理解引數的排列順序和組合。此外,如果你在未來增加更多選項,這個引數列表可能會繼續增長。這稱為伸縮建構函式反模式
程式程式碼示例
上面的N引數建構函式示例,明智的選擇是使用建造者模式。首先,我們有我們想要創造的英雄
public final class Hero { private final Profession profession; private final String name; private final HairType hairType; private final HairColor hairColor; private final Armor armor; private final Weapon weapon; private Hero(Builder builder) { this.profession = builder.profession; this.name = builder.name; this.hairColor = builder.hairColor; this.hairType = builder.hairType; this.weapon = builder.weapon; this.armor = builder.armor; } }
然後我們設計建造者
public static class Builder { private final Profession profession; private final String name; private HairType hairType; private HairColor hairColor; private Armor armor; private Weapon weapon; public Builder(Profession profession, String name) { if (profession == null || name == null) { throw new IllegalArgumentException("profession and name can not be null"); } this.profession = profession; this.name = name; } public Builder withHairType(HairType hairType) { this.hairType = hairType; return this; } public Builder withHairColor(HairColor hairColor) { this.hairColor = hairColor; return this; } public Builder withArmor(Armor armor) { this.armor = armor; return this; } public Builder withWeapon(Weapon weapon) { this.weapon = weapon; return this; } public Hero build() { return new Hero(this); } }
最後我們可以這樣來建造Hero:
Hero mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build();
應用場景
當遇到如下的情況你應該考慮使用建造者模式:
- 建立複雜物件的演算法應該獨立於組成物件的部件以及它們是如何組裝的
- 構建過程必須允許對構建的物件進行不同的表示
Java中的現例項子
- ofollow,noindex">java.lang.StringBuilder
- java.nio.ByteBuffer 還有其他類似的buffers 比如 FloatBuffer, IntBuffer 等等.
- java.lang.StringBuffer
- All implementations of java.lang.Appendable
- Apache Camel builders
優缺點
優點:1、建造者獨立,易擴充套件。 2、便於控制細節風險。
缺點:1、產品必須有共同點,範圍有限制。 2、如內部變化複雜,會有很多的建造類。
寫在最後
建造者模式又稱之為生成器模式,一般來說有三個角色: 建造者、具體的建造者、監工角色 ,為了形象的說明這三個角色的結構和定義我們自己來設計一個程式例項。
我們假設要製作一份宣傳文案,一份文案可以包含一個或多個文件,文件有三種類型:文字文件、圖表文件、圖片文件。根據不同型別文件的組合我們有不同型別的文案生成,如文字文案由純文字文件組成,圖表文案由圖表文件和圖片文件組成,混合文案由文字文件、圖表文件、圖片文件三者共同組成。
不同型別的文件由不同的書寫工具書寫,如文字文件由MicrosoftWord工具編寫,圖表文件由MicrosoftExcel工具編寫,圖片文件由PhotoShop工具編寫。
按照上面的假設需求,我們首先設計程式類圖如下:

image.png
接下來編寫程式
步驟一:建立文件介面和編寫工具介面
public interface Document { /** * * @return 文件名稱 */ String name(); /** * * @return 文件型別 */ String type(); /** * * @return 書寫工具 */ WriteTool writeTool(); } public interface WriteTool { /** * * @return 返回書寫工具[名稱]+"write" */ String write(); }
步驟二:編寫WriteTool介面的實現類
public class MicrosoftWord implements WriteTool{ @Override public String write() { return "MicrosoftWord write"; } } public class MicrosoftExcel implements WriteTool { @Override public String write() { return "MicrosoftExcel write"; } } public class PhotoShop implements WriteTool{ @Override public String write() { return "PhotoShop write"; } }
步驟三:編寫Document介面的實現類
public class Chart implements Document { @Override public String name() { return "chart document"; } @Override public String type() { return "table"; } @Override public WriteTool writeTool() { return new MicrosoftExcel(); } } public class Image implements Document { @Override public String name() { return "image document"; } @Override public String type() { return "image"; } @Override public WriteTool writeTool() { return new PhotoShop(); } } public class Wordimplements Document{ @Override public String name() { return "word document"; } @Override public String type() { return "text"; } @Override public WriteTool writeTool() { return new MicrosoftWord(); } }
步驟四:編寫建造者CopyWriter類
/** * 不同的文案包含一些不同型別的文件 * 定義建造物件的方式方法 */ public class CopyWriter { //包含的文件 privateList<Document> documents = new ArrayList<>(); //名字 private String name; //文案型別文字 圖表 混合 private String type; public CopyWriter(String name,String type){ this.name = name; this.type = type; } //新增文件 public CopyWriter addDocument(Document document) { if (null == document){ throw new IllegalArgumentException("documnet can not be null"); } this.documents.add(document); return this; } public String name(){ return this.name; } public String getType(){ return this.type; } //展示文案包含的文件資訊 public void showDocuments(){ for (Document doc:documents) { System.out.print("name:"+doc.name()); System.out.print(" type:"+doc.type()); System.out.println(" writeTool:"+doc.writeTool().write()); } } }
步驟五:編寫監工CopyWriterBuilder
//將一個複雜物件的構建過程與其表示相分離 public class CopyWriterBuilder { /** * 準備文字型別的文案 * @return */ public CopyWriter prepareTextCopyWriter(){ CopyWriter copyWriter = new CopyWriter("TextCopyWriter","text"); //文字型別的文案只需要準備文字文件即可 copyWriter.addDocument(new Word()); return copyWriter; } /** * 準備圖表型別的文案 * @return */ public CopyWriter prepareTableCopyWriter(){ CopyWriter copyWriter = new CopyWriter("TableCopyWriter","table"); //圖表型別的文案需要準備圖表文件和圖片文件 copyWriter.addDocument(new Chart()).addDocument(new Image()); return copyWriter; } /** * 準備混合型別的文案 包含文字和圖表 * @return */ public CopyWriter prepareMixCopyWriter(){ CopyWriter copyWriter = new CopyWriter("MixCopyWriter","Mix"); //圖表型別的文案需要準備圖表文件、圖片文件、文字文件 copyWriter.addDocument(new Chart()).addDocument(new Image()).addDocument(new Word()); return copyWriter; } }
步驟六:最後編寫使用者
public class App { public static void main(String[] args){ CopyWriterBuilder builder = new CopyWriterBuilder(); CopyWriter txtCopyWriter = builder.prepareTextCopyWriter(); System.out.println(txtCopyWriter.name()); txtCopyWriter.showDocuments(); System.out.println("---------------------------------"); CopyWriter tableCopyWriter = builder.prepareTableCopyWriter(); System.out.println(tableCopyWriter.name()); tableCopyWriter.showDocuments(); System.out.println("---------------------------------"); CopyWriter mixCopyWriter = builder.prepareMixCopyWriter(); System.out.println(mixCopyWriter.name()); mixCopyWriter.showDocuments(); } }
執行App輸出結果如下:
TextCopyWriter name:word document type:text writeTool:MicrosoftWord write --------------------------------- TableCopyWriter name:chart document type:table writeTool:MicrosoftExcel write name:image document type:image writeTool:PhotoShop write --------------------------------- MixCopyWriter name:chart document type:table writeTool:MicrosoftExcel write name:image document type:image writeTool:PhotoShop write name:word document type:text writeTool:MicrosoftWord write
下一章節我將介紹 原型模式(Prototype Pattern) 這將是最後一個建立型模式
碼字不易,各位看官如果喜歡的話,請給點個喜歡:heart:,關注下我。我將努力持續不斷的為大家更新完此係列