設(shè)計模式系列—建造者模式
前言
- 23種設(shè)計模式速記
- 單例(singleton)模式
- 工廠方法(factory method)模式
- 抽象工廠(abstract factory)模式
23種設(shè)計模式快速記憶的請看上面第一篇,本篇和大家一起來學習建造者模式相關(guān)內(nèi)容。
模式定義
將一個復雜對象的創(chuàng)建與他的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
用戶只需要給出指定復雜對象的類型和內(nèi)容;
建造者模式負責按順序創(chuàng)建復雜對象(把內(nèi)部的建造過程和細節(jié)隱藏起來)
解決的問題
- 降低創(chuàng)建復雜對象的復雜度
- 隔離了創(chuàng)建對象的構(gòu)建過程 & 表示
從而:
- 方便用戶創(chuàng)建復雜的對象(不需要知道實現(xiàn)過程)
- 代碼復用性 & 封裝性(將對象構(gòu)建過程和細節(jié)進行封裝 & 復用)
模式組成
- 指揮者(Director)直接和客戶(Client)進行需求溝通;
- 溝通后指揮者將客戶創(chuàng)建產(chǎn)品的需求劃分為各個部件的建造請求(Builder);
- 將各個部件的建造請求委派到具體的建造者(ConcreteBuilder);
- 各個具體建造者負責進行產(chǎn)品部件的構(gòu)建;
- 最終構(gòu)建成具體產(chǎn)品(Product)。
實例說明
實例概況
- 背景 小張希望去中關(guān)村買一臺組裝的臺式主機
- 過程
- 中關(guān)村老板(Diretor)和小張(Client)進行需求溝通(買來打游戲?學習?看片?)
- 了解需求后,電腦城老板將小張需要的主機劃分為各個部件(Builder)的建造請求(CPU、主板......)
- 指揮裝機人員(ConcreteBuilder)去構(gòu)建組件;
- 將組件組裝起來成小張需要的電腦(Product)
使用步驟
步驟1:定義具體產(chǎn)品類(Product):電腦
- class Computer{
- //電腦組件的集合
- private List<String> parts = new ArrayList<String>();
- //用于將組件組裝到電腦里
- public void Add(String part){
- parts.add(part);
- }
- public void Show(){
- for (int i = 0;i<parts.size();i++){
- System.out.println("組件" + parts.get(i) + "裝好了");
- }
- System.out.println("電腦組裝完成,請驗收");
- }
- }
步驟2:定義組裝的過程(Builder):組裝電腦的過程
- abstract class Builder {
- //第一步:裝CPU
- //聲明為抽象方法,具體由子類實現(xiàn)
- public abstract void BuildCPU();
- //第二步:裝主板
- //聲明為抽象方法,具體由子類實現(xiàn)
- public abstract void BuildMainboard();
- //第三步:裝硬盤
- //聲明為抽象方法,具體由子類實現(xiàn)
- public abstract void BuildHD();
- //返回產(chǎn)品的方法:獲得組裝好的電腦
- public abstract Computer GetComputer();
- }
步驟3: 中關(guān)村老板委派任務(wù)給裝機人員(Director)
- class Director{
- //指揮裝機人員組裝電腦
- public void Construct(Builder builder){
- builder. BuildCPU();
- builder.BuildMainboard();
- builder.BuildHD();
- }
- }
步驟4: 創(chuàng)建具體的建造者(ConcreteBuilder):裝機人員
- class ConcreteBuilder extends Builder{
- //創(chuàng)建產(chǎn)品實例
- Computer computer = new Computer();
- //組裝產(chǎn)品
- @Override
- public void BuildCPU(){
- computer.Add("組裝CPU");
- }
- @Override
- public void BuildMainboard() {
- computer.Add("組裝主板");
- }
- @Override
- public void BuildHD() {
- computer.Add("組裝主板");
- }
- //返回組裝成功的電腦
- @Override
- public Computer GetComputer(){
- return computer;
- }
- }
步驟5:客戶端調(diào)用-小張到電腦城找老板買電腦
- public class BuilderPattern<builder> {
- public static void main(String[] args) {
- // 步驟5:客戶端調(diào)用-小張到電腦城找老板買電腦
- //逛了很久終于發(fā)現(xiàn)一家合適的電腦店
- //找到該店的老板和裝機人員
- Director director = new Director();
- Builder builder = new ConcreteBuilder();
- //溝通需求后,老板叫裝機人員去裝電腦
- director.Construct(builder);
- //裝完后,組裝人員搬來組裝好的電腦
- Computer computer = builder.GetComputer();
- //組裝人員展示電腦給小張看
- computer.Show();
- }
- }
輸出結(jié)果
- 組件CPU裝好了
- 組件主板裝好了
- 組件硬盤裝好了
- 電腦組裝完成,請驗收
優(yōu)點
- 良好的封裝性:建造者對客戶端屏蔽了產(chǎn)品內(nèi)部組成的細節(jié),客戶端不用關(guān)心每一個具體的產(chǎn)品內(nèi)部是如何實現(xiàn)的。
- 符合開閉原則
- 便于控制細節(jié)風險:由于建造者是相互獨立的,因此可以對建造過程逐步細化,而不對其他的模塊產(chǎn)生任何影響。
每一個具體建造者都相對獨立,而與其他的具體建造者無關(guān),因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產(chǎn)品對象。
缺點
- 建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點,其組成部分相似;如果產(chǎn)品之間的差異性很大,則不適合使用建造者模式,因此其使用范圍受到一定的限制。
- 如果產(chǎn)品的內(nèi)部變化復雜,可能會導致需要定義很多具體建造者類來實現(xiàn)這種變化,導致系統(tǒng)變得很龐大。
應(yīng)用場景
- 需要生成的對象具有復雜的內(nèi)部結(jié)構(gòu)
- 需要生成的對象內(nèi)部屬性本身相互依賴
- 與不可變對象配合使用
與工廠方法模式的區(qū)別
建造者模式最主要的功能是基本方法的調(diào)用順序安排,基本方法已經(jīng)實現(xiàn),我們可以理解為零件的裝配,順序不同產(chǎn)生的對象也不同;而工廠方法的注重點是創(chuàng)建,創(chuàng)建零件是其主要職責,不關(guān)心組裝順序。
源碼中的應(yīng)用
- # jdk
- java.lang.StringBuilder
- # Spring源碼
- org.springframework.web.servlet.mvc.method.RequestMappingInfo
- org.springframework.beans.factory.support.BeanDefinitionBuilder
- ......
StringBuilder源碼分析
在jdk中StringBuilder類的實現(xiàn)中,采用建造者模式的思想。具體分析如下:
StringBuilder類繼承自AbstractStringBuilder,而AbstractStringBuilder實現(xiàn)了Appendable接口。AbstractStringBuilder雖然是一個抽象類,但是它實現(xiàn)了Appendable接口中的各個append()方法,因此在這里Appendable接口是一個抽象建造者,而AbstractStringBuilder是建造者,只是不能實例化。對于StringBuilder類,它既充當了指揮者角色,同時充當了具體的建造者,建造方法的具體實現(xiàn)是由AbstractStringBuilder完成,StringBuilder繼承了AbstractStringBuilder。
Appendable接口
- public interface Appendable {
- Appendable append(CharSequence csq) throws IOException;
- Appendable append(CharSequence csq, int start, int end) throws IOException;
- Appendable append(char c) throws IOException;
- }
AbstractStringBuilder類
- abstract class AbstractStringBuilder implements Appendable, CharSequence {
- char[] value;//The value is used for character storage.
- int count;//The count is the number of characters used.
- AbstractStringBuilder() { }
- AbstractStringBuilder(int capacity) {
- value = new char[capacity];
- }
- public AbstractStringBuilder append(String str) {
- if (str == null)
- return appendNull();
- int len = str.length();
- ensureCapacityInternal(count + len);
- str.getChars(0, len, value, count);
- count += len;
- return this;
- }
- private AbstractStringBuilder appendNull() {
- int c = count;
- ensureCapacityInternal(c + 4);
- final char[] value = this.value;
- value[c++] = 'n';
- value[c++] = 'u';
- value[c++] = 'l';
- value[c++] = 'l';
- count = c;
- return this;
- }
- private void ensureCapacityInternal(int minimumCapacity) {
- // overflow-conscious code
- if (minimumCapacity - value.length > 0) {
- value = Arrays.copyOf(value,
- newCapacity(minimumCapacity));
- }
- }
- public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
- if (srcBegin < 0) {
- throw new StringIndexOutOfBoundsException(srcBegin);
- }
- if (srcEnd > value.length) {
- throw new StringIndexOutOfBoundsException(srcEnd);
- }
- if (srcBegin > srcEnd) {
- throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
- }
- System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
- }
- // 此次省略......
- }
StringBuilder類:
- public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {
- //雖說是重寫,但還是調(diào)用的AbstractStringBuilder方法
- @Override
- public StringBuilder append(String str) {
- super.append(str);
- return this;
- }
- }
PS:以上代碼提交在 Github :
https://github.com/Niuh-Study/niuh-designpatterns.git