設計模式 | 建造者模式及典型應用

建造者模式

建造者模式(Builder Pattern):將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。建造者模式是一種對象建立型模式。java

建造者模式一步一步建立一個複雜的對象,它容許用戶只經過指定複雜對象的類型和內容就能夠構建它們,用戶不須要知道內部的具體構建細節。sql

角色

Builder(抽象建造者):它爲建立一個產品Product對象的各個部件指定抽象接口,在該接口中通常聲明兩類方法,一類方法是buildPartX(),它們用於建立複雜對象的各個部件;另外一類方法是getResult(),它們用於返回複雜對象。Builder既能夠是抽象類,也能夠是接口。數據庫

ConcreteBuilder(具體建造者):它實現了Builder接口,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜對象,也能夠提供一個方法返回建立好的複雜產品對象。apache

Product(產品角色):它是被構建的複雜對象,包含多個組成部件,具體建造者建立該產品的內部表示並定義它的裝配過程。編程

Director(指揮者):指揮者又稱爲導演類,它負責安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,能夠在其construct()建造方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端通常只須要與指揮者進行交互,在客戶端肯定具體建造者的類型,並實例化具體建造者對象(也能夠經過配置文件和反射機制),而後經過指揮者類的構造函數或者Setter方法將該對象傳入指揮者類中。設計模式

在建造者模式的定義中提到了複雜對象,那麼什麼是複雜對象?簡單來講,複雜對象是指那些包含多個成員屬性的對象,這些成員屬性也稱爲部件或零件,如汽車包括方向盤、發動機、輪胎等部件,電子郵件包括髮件人、收件人、主題、內容、附件等部件緩存

示例

產品角色 Computer安全

public class Computer {
    private String brand;
    private String cpu;
    private String mainBoard;
    private String hardDisk;
    private String displayCard;
    private String power;
    private String memory;
    // 省略 getter, setter, toString
}

抽象建造者 builderbash

public abstract class Builder {
    protected Computer computer = new Computer();

    public abstract void buildBrand();
    public abstract void buildCPU();
    public abstract void buildMainBoard();
    public abstract void buildHardDisk();
    public abstract void buildDisplayCard();
    public abstract void buildPower();
    public abstract void buildMemory();
    public Computer createComputer({
        return computer;
    }
}

具體建造者 DellComputerBuilderASUSComputerBuilder,分別建造戴爾電腦和華碩電腦微信

public class DellComputerBuilder extends Builder {
    @Override
    public void buildBrand() {
        computer.setBrand("戴爾電腦");
    }
    @Override
    public void buildCPU() {
        computer.setCpu("i5-8300H 四核");
    }
    @Override
    public void buildMainBoard() {
        computer.setMainBoard("戴爾主板");
    }
    @Override
    public void buildHardDisk() {
        computer.setHardDisk("1T + 128GB SSD");
    }
    @Override
    public void buildDisplayCard() {
        computer.setDisplayCard("GTX1060 獨立6GB");
    }
    @Override
    public void buildPower() {
        computer.setPower("4芯 鋰離子電池 180W AC適配器");
    }
    @Override
    public void buildMemory() {
        computer.setMemory("4G + 4G");
    }
}

public class ASUSComputerBuilder extends Builder{
    @Override
    public void buildBrand() {
        computer.setBrand("華碩電腦");
    }
    @Override
    public void buildCPU() {
        computer.setCpu("Intel 第8代 酷睿");
    }
    @Override
    public void buildMainBoard() {
        computer.setMainBoard("華碩主板");
    }
    @Override
    public void buildHardDisk() {
        computer.setHardDisk("256GB SSD");
    }
    @Override
    public void buildDisplayCard() {
        computer.setDisplayCard("MX150 獨立2GB");
    }
    @Override
    public void buildPower() {
        computer.setPower("3芯 鋰離子電池 65W AC適配器");
    }
    @Override
    public void buildMemory() {
        computer.setMemory("1 x SO-DIMM  8GB");
    }
}

指揮者 ComputerDirector,指揮構建過程

public class ComputerDirector {
    public Computer construct(Builder builder) {
        // 逐步構建複雜產品對象
        Computer computer;
        builder.buildBrand();
        builder.buildCPU();
        builder.buildDisplayCard();
        builder.buildHardDisk();
        builder.buildMainBoard();
        builder.buildMemory();
        builder.buildPower();
        computer = builder.createComputer();
        return computer;
    }
}

客戶端測試

public class Test {
    public static void main(String[] args{
        ComputerDirector director = new ComputerDirector();

        Builder asusBuilder = new ASUSComputerBuilder();
        Computer asusComputer = director.construct(asusBuilder);
        System.out.println(asusComputer.toString());

        Builder dellBuilder = new DellComputerBuilder();
        Computer dellComputer = director.construct(dellBuilder);
        System.out.println(dellComputer.toString());
    }
}

輸出

Computer{brand='華碩電腦', cpu='Intel 第8代 酷睿', mainBoard='華碩主板', hardDisk='256GB SSD', displayCard='MX150 獨立2GB', power='3芯 鋰離子電池 65W AC適配器', memory='1 x SO-DIMM  8GB'}
Computer{brand='戴爾電腦', cpu='i5-8300H 四核', mainBoard='戴爾主板', hardDisk='1T + 128GB SSD', displayCard='GTX1060 獨立6GB', power='4芯 鋰離子電池 180W AC適配器', memory='4G + 4G'}

能夠經過反射機制和配置文件配合,建立具體建造者對象

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ComputerDirector director = new ComputerDirector();

        // 從數據庫或者配置文件中讀取具體建造者類名
        Class c = Class.forName("com.designpattern.ASUSComputerBuilder");
        Builder asusBuilder = (Builder) c.newInstance();
        Computer asusComputer = director.construct(asusBuilder);
        System.out.println(asusComputer.toString());
    }
}
示例.建造者模式類圖

建造者模式總結

建造者模式的主要優勢以下:

  • 在建造者模式中,客戶端沒必要知道產品內部組成的細節,將產品自己與產品的建立過程解耦,使得相同的建立過程能夠建立不一樣的產品對象。

  • 每個具體建造者都相對獨立,而與其餘的具體建造者無關,所以能夠很方便地替換具體建造者或增長新的具體建造者,用戶使用不一樣的具體建造者便可獲得不一樣的產品對象。因爲指揮者類針對抽象建造者編程,增長新的具體建造者無須修改原有類庫的代碼,系統擴展方便,符合 "開閉原則"。

  • 能夠更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不一樣的方法中,使得建立過程更加清晰,也更方便使用程序來控制建立過程。

建造者模式的主要缺點以下:

  • 建造者模式所建立的產品通常具備較多的共同點,其組成部分類似,若是產品之間的差別性很大,例如不少組成部分都不相同,不適合使用建造者模式,所以其使用範圍受到必定的限制。

  • 若是產品的內部變化複雜,可能會致使須要定義不少具體建造者類來實現這種變化,致使系統變得很龐大,增長系統的理解難度和運行成本。

適用場景

  • 須要生成的產品對象有複雜的內部結構,這些產品對象一般包含多個成員屬性。

  • 須要生成的產品對象的屬性相互依賴,須要指定其生成順序。

  • 對象的建立過程獨立於建立該對象的類。在建造者模式中經過引入了指揮者類,將建立過程封裝在指揮者類中,而不在建造者類和客戶類中。

  • 隔離複雜對象的建立和使用,並使得相同的建立過程能夠建立不一樣的產品。

建造者模式的典型應用和源碼分析

java.lang.StringBuilder 中的建造者模式

StringBuilder 的繼承實現關係以下所示

StringBuilder的類圖

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;
}

StringBuilder 中的 append 方法使用了建造者模式,不過裝配方法只有一個,並不算複雜,append 方法返回的是 StringBuilder 自身

public final class StringBuilder extends AbstractStringBuilder implements java.io.SerializableCharSequence {
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    // ...省略...
}

StringBuilder 的父類 AbstractStringBuilder 實現了 Appendable 接口

abstract class AbstractStringBuilder implements AppendableCharSequence {
    char[] value;
    int count;

    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 void ensureCapacityInternal(int minimumCapacity{
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
    // ...省略...
}

咱們能夠看出,Appendable 爲抽象建造者,定義了建造方法,StringBuilder 既充當指揮者角色,又充當產品角色,又充當具體建造者,建造方法的實現由 AbstractStringBuilder 完成,而 StringBuilder 繼承了 AbstractStringBuilder

java.lang.StringBuffer 中的建造者模式

StringBuffer 繼承與實現關係以下

StringBuffer的類圖

這分明就與 StringBuilder 同樣嘛!

那它們有什麼不一樣呢?

public final class StringBuffer extends AbstractStringBuilder implements java.io.SerializableCharSequence {
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    //...省略...
}

StringBuffer 的源碼如上,它們的區別就是: StringBuffer 中的 append 加了 synchronized 關鍵字,因此StringBuffer 是線程安全的,而 StringBuilder 是非線程安全的

StringBuffer 中的建造者模式與 StringBuilder 是一致的

Google Guava 中的建造者模式

ImmutableSet 不可變Set的主要方法以下

ImmutableSet方法列表

ImmutableSet 類中 of, copyOf 等方法返回的是一個 ImmutableSet 對象,這裏是一個建造者模式,所構建的複雜產品對象爲 ImmutableSet

ImmutableSet 的內部類 ImmutableSet.Builder 以下所示

public static class Builder<Eextends ArrayBasedBuilder<E{
    @CanIgnoreReturnValue
    public ImmutableSet.Builder<E> add(E... elements) {
        super.add(elements);
        return this;
    }

    @CanIgnoreReturnValue
    public ImmutableSet.Builder<E> addAll(Iterator<? extends E> elements) {
        super.addAll(elements);
        return this;
    }

    public ImmutableSet<E> build() {
        ImmutableSet<E> result = ImmutableSet.construct(this.size, this.contents);
        this.size = result.size();
        return result;
    }
    //...省略...
}

其中的 addaddAll等方法返回的是 ImmutableSet.Builder 對象自己,而 build 則返回 ImmutableSet 對象,因此 ImmutableSet.Builder 是具體建造者,addaddAll等方法則至關於buildPartX(),是裝配過程當中的一部分,build 方法則是 getResult(),返回最終建立好的複雜產品對象

ImmutableSet 使用示例以下:

public class Test2 {
    public static void main(String[] args{
        Set<String> set = ImmutableSet.<String>builder().add("a").add("a").add("b").build();
        System.out.println(set);
        // [a, b]
    }
}

再來看一個,通常建立一個 guava緩存 的寫法以下所示

final static Cache<IntegerStringcache = CacheBuilder.newBuilder()
        //設置cache的初始大小爲10,要合理設置該值  
        .initialCapacity(10)
        //設置併發數爲5,即同一時間最多隻能有5個線程往cache執行寫入操做  
        .concurrencyLevel(5)
        //設置cache中的數據在寫入以後的存活時間爲10秒  
        .expireAfterWrite(10, TimeUnit.SECONDS)
        //構建cache實例  
        .build();

這裏很明顯,咱們不用看源碼就能夠知道這裏是一個典型的建造者模式,CacheBuilder.newBuilder() 建立了一個具體建造者,.initialCapacity(10).concurrencyLevel(5).expireAfterWrite(10, TimeUnit.SECONDS) 則是構建過程,最終的 .build() 返回建立完成的複雜產品對象

看看源碼是否是符合咱們的猜想

public final class CacheBuilder<K, V{
    // 建立一個具體建造者
    public static CacheBuilder<Object, Object> newBuilder() {
        return new CacheBuilder();
    }
    // 建造過程之一
    public CacheBuilder<K, V> initialCapacity(int initialCapacity) {
        Preconditions.checkState(this.initialCapacity == -1"initial capacity was already set to %s"this.initialCapacity);
        Preconditions.checkArgument(initialCapacity >= 0);
        this.initialCapacity = initialCapacity;
        return this;
    }
    // 建造過程之一
    public CacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {
        Preconditions.checkState(this.concurrencyLevel == -1"concurrency level was already set to %s"this.concurrencyLevel);
        Preconditions.checkArgument(concurrencyLevel > 0);
        this.concurrencyLevel = concurrencyLevel;
        return this;
    }
    // 建造過程之一
    public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
        Preconditions.checkState(this.expireAfterWriteNanos == -1L, "expireAfterWrite was already set to %s ns"this.expireAfterWriteNanos);
        Preconditions.checkArgument(duration >= 0L, "duration cannot be negative: %s %s", duration, unit);
        this.expireAfterWriteNanos = unit.toNanos(duration);
        return this;
    }
    // 建造完成,返回建立完的複雜產品對象
    public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
        this.checkWeightWithWeigher();
        this.checkNonLoadingCache();
        return new LocalManualCache(this);
    }
    // ...省略...
}

很明顯符合咱們的猜想,initialCapacity()concurrencyLevel()expireAfterWrite() 等方法對傳進來的參數進行處理和設置,返回 CacheBuilder 對象自己,build 則把 CacheBuilder 對象 做爲參數,new 了一個 LocalManualCache 對象返回

mybatis 中的建造者模式

咱們來看 org.apache.ibatis.session 包下的 SqlSessionFactoryBuilder

SqlSessionFactoryBuilder的方法

裏邊不少重載的 build 方法,返回值都是 SqlSessionFactory,除了最後兩個全部的 build 最後都調用下面這個 build 方法

    public SqlSessionFactory build(Reader reader, String environment, Properties properties{
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
            try {
                reader.close();
            } catch (IOException var13) {
                ;
            }
        }
        return var5;
    }

其中最重要的是 XMLConfigBuilderparse 方法,代碼以下

public class XMLConfigBuilder extends BaseBuilder {
    public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

    private void parseConfiguration(XNode root) {
        try {
            Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
            this.propertiesElement(root.evalNode("properties"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }
    // ...省略...
}

parse 方法最終要返回一個 Configuration 對象,構建 Configuration 對象的建造過程都在 parseConfiguration 方法中,這也就是 Mybatis 解析 XML配置文件 來構建 Configuration 對象的主要過程

因此 XMLConfigBuilder 是建造者 SqlSessionFactoryBuilder 中的建造者,複雜產品對象分別是 SqlSessionFactoryConfiguration

後記

點擊[閱讀原文]可訪問個人我的博客:http://laijianfeng.org

關注【小旋鋒】微信公衆號,及時接收博文推送

參考:  
劉偉:設計模式Java版  
慕課網java設計模式精講 Debug 方式+內存分析


本文分享自微信公衆號 - 小旋鋒(whirlysBigData)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索