「設(shè)計(jì)模式(六) - Builder模式」

「設(shè)計(jì)模式(六) - Builder模式」

一夺艰、可定制化的

電腦的組裝在生活中并不陌生,大家都有電腦,當(dāng)然需求不一樣配置也不一樣月而。以Macbook Pro為例,像UI設(shè)計(jì)對圖像模塊GPU要求比較高议纯,跑IDEA的對內(nèi)存要求就比較高父款,可能會加裝32G內(nèi)存更高的就是64G了。如果是對付日常的辦公瞻凤,刷劇那默認(rèn)的配置就已經(jīng)足夠了憨攒,如8G標(biāo)配。類似的在軟件開發(fā)過程中阀参,需要根據(jù)需要自定義搭配不同的選擇來構(gòu)建對象的一般能夠用Builder模式很好的解釋肝集,看看具體的定義是怎樣的。

二蛛壳、Builder模式

維基百科

[The builder pattern is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. The intent of the Builder design pattern is to separate the construction of a complex object from its representation. It is one of the Gang of Four design patterns.]

通俗的表示:即將一個(gè)復(fù)雜對象的構(gòu)建與表示相分離開杏瞻,同樣的構(gòu)建過程根據(jù)需要可以創(chuàng)建不同的表示。稱之為Builder模式建造者模式衙荐。

三捞挥、結(jié)構(gòu)組成
  • 結(jié)構(gòu)類圖:


    Builder模式.png
  • 構(gòu)造器接口Builder:通常會包含構(gòu)建部件模塊的抽象方法,以及對象創(chuàng)建完成后的結(jié)果方法getResult()忧吟。

  • 構(gòu)造器具體實(shí)現(xiàn)類ConcreteBuilder:對Builder接口的具體實(shí)現(xiàn)砌函,構(gòu)建產(chǎn)品Product各個(gè)部件,組裝成完整的產(chǎn)品對象溜族,并通過getResult()返回具體的對象讹俊。

  • 產(chǎn)品的抽象接口Product:需要構(gòu)造的對象,一般包含多個(gè)組成部分煌抒,構(gòu)成較為復(fù)雜的對象仍劈。

  • 定制者(指揮者)Director:決定了Product的具體創(chuàng)建組成,例如包含何種組件摧玫。指導(dǎo)出Product復(fù)雜對象的創(chuàng)建耳奕,類圖表示較為清晰绑青;一般使用Builder來完成創(chuàng)建過程。

四屋群、代碼實(shí)現(xiàn)
1.設(shè)計(jì)一個(gè)簡單的文件導(dǎo)出系統(tǒng)

根據(jù)需要配置對應(yīng)的文檔系統(tǒng)并導(dǎo)出文件闸婴,例如導(dǎo)出純文本內(nèi)容.text.html等文件芍躏。

  • 元素Element對應(yīng)這里的Product邪乍,即產(chǎn)品的接口
public interface Element {
  //no method
}

1.為提供默認(rèn)的方法,由實(shí)現(xiàn)類自行決定實(shí)現(xiàn)何種細(xì)節(jié)对竣。

  • 元素Element具體實(shí)現(xiàn)類-文本
/**
 * Created by Sai
 * on: 25/01/2022 00:48.
 * Description:
 */
public class Text implements Element {
    private final String content;

    public Text(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}
  • 元素Element具體實(shí)現(xiàn)類-圖片
/**
 * Created by Sai
 * on: 25/01/2022 00:49.
 * Description:
 */
public class Image implements Element {
    private final String source;

    public Image(String source) {
        this.source = source;
    }

    public String getSource() {
        return source;
    }
}
  • 建造器接口Builder庇楞,這里既然是文檔類型的建造器,命名規(guī)范一點(diǎn)的話則為DocumentBuilder否纬,下同:
/**
 * Created by Sai
 * on: 25/01/2022 00:52.
 * Description:
 */
public interface DocumentBuilder {
    void addText(Text text);

    void addImage(Image image);

    String getResult();
}

1.分別提供了兩個(gè)添加內(nèi)容的方法吕晌,addTextaddImage临燃。

2.提供一個(gè)返回具體內(nèi)容的方法睛驳,getResult

  • 建造器的具體實(shí)現(xiàn)-純文本文檔建造器
public class TextDocumentBuilder implements DocumentBuilder {

    private final StringBuilder builder = new StringBuilder();

    @Override
    public void addText(Text text) {
        builder.append(text.getContent());
    }

    @Override
    public void addImage(Image image) {
        //empty implements
    }

    @Override
    public String getResult() {
        return builder.toString();
    }
}

1.其中純文本內(nèi)容不支持添加圖片膜廊,添加圖片方法則未實(shí)現(xiàn)乏沸,為空方法。

  • 由于html結(jié)構(gòu)相對于純文本來說較為復(fù)雜爪瓜,包括標(biāo)簽蹬跃,段落等等,如果完全依賴于Element不利于擴(kuò)展铆铆,實(shí)現(xiàn)起來比較麻煩蝶缀,因此對于html類型的文檔文件,單獨(dú)實(shí)現(xiàn)自己的一套元素組件薄货。以Element為媒介動(dòng)態(tài)添加進(jìn)去扼劈。

  • 單獨(dú)實(shí)現(xiàn)html特有的元素HtmlElement

public class HtmlElement {
  //empty method
}
  • Html圖片元素組件HtmlImage繼承自HtmlElement
public class HtmlImage extends HtmlElement {
    private final String source;

    public HtmlImage(String source) {
        this.source = source;
    }

    @Override
    public String toString() {
        return String.format("<img src=\"%s\" />", source);
    }
}
  • Html段落文本組件HtmlParagraph同樣繼承自HtmlElement
public class HtmlParagraph extends HtmlElement {
    private final String text;

    public HtmlParagraph(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return String.format("<p>%s</p>", text);
    }
}
  • 一份完整的Html文檔可能包含多種HtmlParagraphHtmlImage的集合,定義html文檔文件類-HtmlDocument
public class HtmlDocument {
    private final List<HtmlElement> elements = new ArrayList<>();

    public void add(HtmlElement element) {
        elements.add(element);
    }

    @Override
    public String toString() {
        var builder = new StringBuilder();
        builder.append("<html>");
        for (HtmlElement element : elements)
            builder.append(element.toString());
        builder.append("</html>");
        return builder.toString();
    }
}
  • 對于html建造器的實(shí)現(xiàn):由于html文檔的特殊性菲驴,雖然依據(jù)類圖的實(shí)現(xiàn)流程需要實(shí)現(xiàn)DocumentBuilder接口,顯然僅僅DocumentBuilder中方法并不能很好的滿足需求的定制骑冗,但是搭配重新定義一套html文檔組裝規(guī)則(HtmlDocument赊瞬、HtmlElement、HtmlImage贼涩、HtmlParagraph)則能夠很好的完成擴(kuò)展:
public class HtmlDocumentBuilder implements DocumentBuilder {
    
    private final HtmlDocument document = new HtmlDocument();

    @Override
    public void addText(Text text) {
        document.add(new HtmlParagraph(text.getContent()));
    }

    @Override
    public void addImage(Image image) {
        document.add(new HtmlImage(image.getSource()));
    }

    @Override
    public String getResult() {
        return document.toString();
    }
}
  • 完整的文檔導(dǎo)出類-Document
public class Document {
    private final List<Element> elements = new ArrayList<>();

    public void add(Element element) {
        elements.add(element);
    }

    public void export(DocumentBuilder builder, String fileName) throws IOException {
        for (Element element : elements) {
            if (element instanceof Text)
                builder.addText((Text) element);
            else if (element instanceof Image)
                builder.addImage((Image) element);
        }
        var writer = new FileWriter(fileName);
        writer.write(builder.getResult());
        writer.close();
    }
}
  • 測試Demo
public class Demo {
    public static void show() throws IOException {
        var document = new Document();
        document.add(new Text("\n\n\n\n快樂二狗??????\nphp才是最好的語言??\n"));
        document.add(new Image("pic1.jpg"));

        document.export(new HtmlDocumentBuilder(), "sai.html");
        //文檔不添加圖片
        document.export(new TextDocumentBuilder(), "sai.txt");
    }

    public static void main(String[] args) throws IOException {
        show();
    }
}

1.簡單的添加了一個(gè)文本內(nèi)容與一個(gè)“圖片內(nèi)容”巧涧,分別組成了純文本的文檔Html的文檔,但文本的文檔是沒有添加圖片的遥倦。

2.導(dǎo)出的文件在同包下谤绳,sai.htmlsai.text文件占锯。

  • “純文本文檔”內(nèi)容


    文本文檔.png
  • “html”文檔


    html文檔.png
2.優(yōu)缺點(diǎn)與局限性思考

1.Builder模式很好的將構(gòu)建與表現(xiàn)相分離,客戶端或者Director可以根據(jù)需要靈活的選擇缩筛,在同一套構(gòu)建算法下產(chǎn)生不同的產(chǎn)品消略,使得表現(xiàn)形式與生產(chǎn)的耦合程度降低。

2.具體的構(gòu)建細(xì)節(jié)包含在系統(tǒng)內(nèi)部瞎抛,客戶端僅僅只需要通過對Builder接口的調(diào)用即可完成創(chuàng)建所需要的對象艺演,降低了系統(tǒng)出錯(cuò)的風(fēng)險(xiǎn)。

3.加強(qiáng)了代碼的復(fù)用性桐臊。

4.當(dāng)然缺點(diǎn)也是很明顯的胎撤,如果對象太過于復(fù)雜,組裝的配件過多往往不好掌控断凶,造成臃腫伤提,結(jié)構(gòu)也不是很清晰。

5.如果改變產(chǎn)品原有的實(shí)現(xiàn)认烁,那么整套流程都需要做出相應(yīng)的調(diào)整肿男,假設(shè)產(chǎn)品本身過于復(fù)雜,那么對于后期的維護(hù)是很不利的砚著。在考慮使用時(shí)應(yīng)根據(jù)實(shí)際情況次伶,對于復(fù)雜且變化頻繁的對象并不適合使用。

五稽穆、實(shí)際應(yīng)用中的變形

實(shí)際使用過程中往往簡化了標(biāo)準(zhǔn)化的構(gòu)建流程冠王,當(dāng)然也是根據(jù)具體的業(yè)務(wù)場景,一般會省略了Director指導(dǎo)類舌镶,Builder接口以及具體的ConcreteBuilder實(shí)現(xiàn)類柱彻,而直接將Builder作為內(nèi)部類實(shí)現(xiàn)在了目標(biāo)產(chǎn)品類之中餐胀。根據(jù)調(diào)用者的選擇得到不同的產(chǎn)品,當(dāng)然這種比較單一否灾,可以說是最為簡單的一種。僅僅是一種思想上的應(yīng)用墨技,或者說也是一種“取巧”的做法惩阶。

  • 自身項(xiàng)目中,對于簡單篩選對象的定制:
public class FilterGroup {
    private int id;
    private String title;
    private boolean supportMultiSelected;
    private List<FilterOrderDTO> filterFactors;

    private FilterGroup(Builder builder) {
        if (builder == null) {
            return;
        }
        this.id = builder.id;
        this.title = builder.title;
        this.supportMultiSelected = builder.supportMultiSelected;
        this.filterFactors = builder.filterFactors;
    }

    public int getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public boolean isSupportMultiSelected() {
        return supportMultiSelected;
    }

    public List<FilterOrderDTO> getFilterFactors() {
        return filterFactors;
    }

    public static class Builder {
        private int id;
        private String title;
        private boolean supportMultiSelected;
        private List<FilterOrderDTO> filterFactors;

        public Builder setId(int id) {
            this.id = id;
            return this;
        }

        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }

        public Builder setSupportMultiSelected(boolean supportMultiSelected) {
            this.supportMultiSelected = supportMultiSelected;
            return this;
        }

        public Builder setFilterFactors(List<FilterOrderDTO> filterFactors) {
            this.filterFactors = filterFactors;
            return this;
        }

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

1.Builder以靜態(tài)內(nèi)部類的形式存在于產(chǎn)品的內(nèi)部断楷,而產(chǎn)品的特征則是:不同的構(gòu)建組成所對應(yīng)的產(chǎn)品不同崭别,表現(xiàn)出來的性質(zhì)也是不同的冬筒。其次Builder中只提供了set方法恐锣,而沒有get方法。

2.目標(biāo)產(chǎn)品FilterGroup的構(gòu)造方法是私有的舞痰,并且以Builder為參數(shù)。另外只提供了get方法而沒有set方法匀奏。

3.即對于目標(biāo)的產(chǎn)品的構(gòu)建是通過Builder來完成的,對于用戶它僅僅是“可讀的”娃善。

4.像實(shí)際開發(fā)中,這種分部分構(gòu)建產(chǎn)品與整體相分離的可以考慮使用Builder模式坯台,核心思想即為整體構(gòu)建與部件之間的分離瘫寝,松散產(chǎn)品構(gòu)建產(chǎn)品表示之間的耦合蜒蕾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載焕阿,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末暮屡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子准夷,更是在濱河造成了極大的恐慌,老刑警劉巖衫嵌,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彻秆,死亡現(xiàn)場離奇詭異,居然都是意外死亡唇兑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來察纯,“玉大人针肥,你說我怎么就攤上這事∠惆椋” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵具帮,是天一觀的道長低斋。 經(jīng)常有香客問我,道長膊畴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任唇跨,我火速辦了婚禮稠通,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘买猖。我一直安慰自己改橘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布飞主。 她就那樣靜靜地躺著奸远,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懒叛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天胖烛,我揣著相機(jī)與錄音诅迷,去河邊找鬼。 笑死罢杉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的滩租。 我是一名探鬼主播利朵,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼猎莲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了樟遣?” 一聲冷哼從身側(cè)響起身笤,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎展鸡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涤久,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忍弛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蔗彤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疯兼。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吧彪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秧倾,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布那先,位于F島的核電站赡艰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苦掘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一楔壤、第九天 我趴在偏房一處隱蔽的房頂上張望惯驼。 院中可真熱鬧,春花似錦祟牲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽言询。三九已至傲宜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間函卒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工虱咧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锚国,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓逸雹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梆砸。 傳聞我的和親對象是個(gè)殘疾皇子园欣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容