建造者模式(Bulider模式)詳解
在軟件開發(fā)過程中有時(shí)需要?jiǎng)?chuàng)建一個(gè)復(fù)雜的對(duì)象犁嗅,這個(gè)復(fù)雜對(duì)象通常由多個(gè)子部件按一定的步驟組合而成。
- 例如晤碘,計(jì)算機(jī)是由 OPU褂微、主板、內(nèi)存园爷、硬盤蕊梧、顯卡、機(jī)箱腮介、顯示器肥矢、鍵盤、鼠標(biāo)等部件組裝而成的叠洗,采購員不可能自己去組裝計(jì)算機(jī)甘改,而是將計(jì)算機(jī)的配置要求告訴計(jì)算機(jī)銷售公司,計(jì)算機(jī)銷售公司安排技術(shù)人員去組裝計(jì)算機(jī)灭抑,然后再交給要買計(jì)算機(jī)的采購員十艾。
生活中這樣的例子很多,如游戲中的不同角色腾节,其性別忘嫉、個(gè)性、能力案腺、臉型庆冕、體型、服裝劈榨、發(fā)型等特性都有所差異访递;還有汽車中的方向盤、發(fā)動(dòng)機(jī)同辣、車架拷姿、輪胎等部件也多種多樣惭载;每封電子郵件的發(fā)件人、收件人响巢、主題描滔、內(nèi)容、附件等內(nèi)容也各不相同踪古。
以上所有這些產(chǎn)品都是由多個(gè)部件構(gòu)成的伴挚,各個(gè)部件可以靈活選擇,但其創(chuàng)建步驟都大同小異灾炭。這類產(chǎn)品的創(chuàng)建無法用前面介紹的工廠模式描述,只有建造者模式可以很好地描述該類產(chǎn)品的創(chuàng)建颅眶。
模式的定義與特點(diǎn)
建造者(Builder)模式的定義:指將一個(gè)復(fù)雜對(duì)象的構(gòu)造與它的表示分離蜈出,使同樣的構(gòu)建過程可以創(chuàng)建不同的表示,這樣的設(shè)計(jì)模式被稱為建造者模式涛酗。它是將一個(gè)復(fù)雜的對(duì)象分解為多個(gè)簡單的對(duì)象铡原,然后一步一步構(gòu)建而成。它將變與不變相分離商叹,即產(chǎn)品的組成部分是不變的燕刻,但每一部分是可以靈活選擇的。
該模式的主要優(yōu)點(diǎn)如下:
- 各個(gè)具體的建造者相互獨(dú)立剖笙,有利于系統(tǒng)的擴(kuò)展卵洗。
- 客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié),便于控制細(xì)節(jié)風(fēng)險(xiǎn)弥咪。
其缺點(diǎn)如下:
- 產(chǎn)品的組成部分必須相同过蹂,這限制了其使用范圍。
- 如果產(chǎn)品的內(nèi)部變化復(fù)雜聚至,該模式會(huì)增加很多的建造者類酷勺。
建造者(Builder)模式和工廠模式的關(guān)注點(diǎn)不同:建造者模式注重零部件的組裝過程,而工廠方法模式更注重零部件的創(chuàng)建過程扳躬,但兩者可以結(jié)合使用脆诉。
模式的結(jié)構(gòu)與實(shí)現(xiàn)
建造者(Builder)模式由產(chǎn)品、抽象建造者贷币、具體建造者击胜、指揮者等 4 個(gè)要素構(gòu)成,現(xiàn)在我們來分析其基本結(jié)構(gòu)和實(shí)現(xiàn)方法役纹。
模式的結(jié)構(gòu)
建造者(Builder)模式的主要角色如下潜的。
- 產(chǎn)品角色(Product):它是包含多個(gè)組成部件的復(fù)雜對(duì)象,由具體建造者來創(chuàng)建其各個(gè)滅部件字管。
- 抽象建造者(Builder):它是一個(gè)包含創(chuàng)建產(chǎn)品各個(gè)子部件的抽象方法的接口啰挪,通常還包含一個(gè)返回復(fù)雜產(chǎn)品的方法 getResult()信不。
- 具體建造者(Concrete Builder):實(shí)現(xiàn) Builder 接口,完成復(fù)雜產(chǎn)品的各個(gè)部件的具體創(chuàng)建方法亡呵。
- 指揮者(Director):它調(diào)用建造者對(duì)象中的部件構(gòu)造與裝配方法完成復(fù)雜對(duì)象的創(chuàng)建抽活,在指揮者中不涉及具體產(chǎn)品的信息。
其結(jié)構(gòu)圖如圖 1 所示锰什。
? 圖1 建造者模式的結(jié)構(gòu)圖
模式的實(shí)現(xiàn)
圖 1 給出了建造者(Builder)模式的主要結(jié)構(gòu)下硕,其相關(guān)類的代碼如下。
(1) 產(chǎn)品角色:包含多個(gè)組成部件的復(fù)雜對(duì)象汁胆。
class Product
{
private String partA;
private String partB;
private String partC;
public void setPartA(String partA)
{
this.partA=partA;
}
public void setPartB(String partB)
{
this.partB=partB;
}
public void setPartC(String partC)
{
this.partC=partC;
}
public void show()
{
//顯示產(chǎn)品的特性
}
}
(2) 抽象建造者:包含創(chuàng)建產(chǎn)品各個(gè)子部件的抽象方法梭姓。
abstract class Builder
{
//創(chuàng)建產(chǎn)品對(duì)象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回產(chǎn)品對(duì)象
public Product getResult()
{
return product;
}
}
(3) 具體建造者:實(shí)現(xiàn)了抽象建造者接口牵囤。
public class ConcreteBuilder extends Builder
{
public void buildPartA()
{
product.setPartA("建造 PartA");
}
public void buildPartB()
{
product.setPartA("建造 PartB");
}
public void buildPartC()
{
product.setPartA("建造 PartC");
}
}
(4) 指揮者:調(diào)用建造者中的方法完成復(fù)雜對(duì)象的創(chuàng)建盔憨。
class Director
{
private Builder builder;
public Director(Builder builder)
{
this.builder=builder;
}
//產(chǎn)品構(gòu)建與組裝方法
public Product construct()
{
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
(5) 客戶類。
public class Client
{
public static void main(String[] args)
{
Builder builder=new ConcreteBuilder();
Director director=new Director(builder);
Product product=director.construct();
product.show();
}
}
模式的應(yīng)用實(shí)例
用建造者(Builder)模式描述客廳裝修例子
分析:客廳裝修是一個(gè)復(fù)雜的過程腹暖,它包含墻體的裝修铸题、電視機(jī)的選擇铡恕、沙發(fā)的購買與布局等《洌客戶把裝修要求告訴項(xiàng)目經(jīng)理探熔,項(xiàng)目經(jīng)理指揮裝修工人一步步裝修,最后完成整個(gè)客廳的裝修與布局烘挫,所以本實(shí)例用建造者模式實(shí)現(xiàn)比較適合诀艰。
這里客廳是產(chǎn)品,包括墻饮六、電視和沙發(fā)等組成部分涡驮。具體裝修工人是具體建造者,他們負(fù)責(zé)裝修與墻喜滨、電視和沙發(fā)的布局捉捅。項(xiàng)目經(jīng)理是指揮者,他負(fù)責(zé)指揮裝修工人進(jìn)行裝修虽风。
另外棒口,客廳類中提供了 show() 方法,可以將裝修效果圖顯示出來(點(diǎn)此下載裝修效果圖的圖片)辜膝∥耷#客戶端程序通過對(duì)象生成器類 ReadXML 讀取 XML 配置文件中的裝修方案數(shù)據(jù)(點(diǎn)此下載 XML 文件),調(diào)用項(xiàng)目經(jīng)理進(jìn)行裝修厂抖。其類圖如圖 2 所示茎毁。
圖2 客廳裝修的結(jié)構(gòu)圖
程序代碼如下:
package Builder;
import java.awt.*;
import javax.swing.*;
public class ParlourDecorator
{
public static void main(String[] args)
{
try
{
Decorator d;
d=(Decorator) ReadXML.getObject();
ProjectManager m=new ProjectManager(d);
Parlour p=m.decorate();
p.show();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
//產(chǎn)品:客廳
class Parlour
{
private String wall; //墻
private String TV; //電視
private String sofa; //沙發(fā)
public void setWall(String wall)
{
this.wall=wall;
}
public void setTV(String TV)
{
this.TV=TV;
}
public void setSofa(String sofa)
{
this.sofa=sofa;
}
public void show()
{
JFrame jf=new JFrame("建造者模式測試");
Container contentPane=jf.getContentPane();
JPanel p=new JPanel();
JScrollPane sp=new JScrollPane(p);
String parlour=wall+TV+sofa;
JLabel l=new JLabel(new ImageIcon("src/"+parlour+".jpg"));
p.setLayout(new GridLayout(1,1));
p.setBorder(BorderFactory.createTitledBorder("客廳"));
p.add(l);
contentPane.add(sp,BorderLayout.CENTER);
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
//抽象建造者:裝修工人
abstract class Decorator
{
//創(chuàng)建產(chǎn)品對(duì)象
protected Parlour product=new Parlour();
public abstract void buildWall();
public abstract void buildTV();
public abstract void buildSofa();
//返回產(chǎn)品對(duì)象
public Parlour getResult()
{
return product;
}
}
//具體建造者:具體裝修工人1
class ConcreteDecorator1 extends Decorator
{
public void buildWall()
{
product.setWall("w1");
}
public void buildTV()
{
product.setTV("TV1");
}
public void buildSofa()
{
product.setSofa("sf1");
}
}
//具體建造者:具體裝修工人2
class ConcreteDecorator2 extends Decorator
{
public void buildWall()
{
product.setWall("w2");
}
public void buildTV()
{
product.setTV("TV2");
}
public void buildSofa()
{
product.setSofa("sf2");
}
}
//指揮者:項(xiàng)目經(jīng)理
class ProjectManager
{
private Decorator builder;
public ProjectManager(Decorator builder)
{
this.builder=builder;
}
//產(chǎn)品構(gòu)建與組裝方法
public Parlour decorate()
{
builder.buildWall();
builder.buildTV();
builder.buildSofa();
return builder.getResult();
}
}
package Builder;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
class ReadXML
{
public static Object getObject()
{
try
{
DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=dFactory.newDocumentBuilder();
Document doc;
doc=builder.parse(new File("src/Builder/config.xml"));
NodeList nl=doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName="Builder."+classNode.getNodeValue();
System.out.println("新類名:"+cName);
Class<?> c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}
程序運(yùn)行結(jié)果如圖 3 所示。
? 圖3 客廳裝修的運(yùn)行結(jié)果
模式的應(yīng)用場景
建造者(Builder)模式創(chuàng)建的是復(fù)雜對(duì)象,其產(chǎn)品的各個(gè)部分經(jīng)常面臨著劇烈的變化七蜘,但將它們組合在一起的算法卻相對(duì)穩(wěn)定谭溉,所以它通常在以下場合使用。
- 創(chuàng)建的對(duì)象較復(fù)雜橡卤,由多個(gè)部件構(gòu)成扮念,各部件面臨著復(fù)雜的變化,但構(gòu)件間的建造順序是穩(wěn)定的碧库。
- 創(chuàng)建復(fù)雜對(duì)象的算法獨(dú)立于該對(duì)象的組成部分以及它們的裝配方式柜与,即產(chǎn)品的構(gòu)建過程和最終的表示是獨(dú)立的。
模式的擴(kuò)展
建造者(Builder)模式在應(yīng)用過程中可以根據(jù)需要改變嵌灰,如果創(chuàng)建的產(chǎn)品種類只有一種弄匕,只需要一個(gè)具體建造者,這時(shí)可以省略掉抽象建造者沽瞭,甚至可以省略掉指揮者角色迁匠。