1. 程式人生 > >設計模式 | 建造者模式及典型應用

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

本文目錄

建造者模式

建造者模式(Builder Pattern):將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。建造者模式是一種物件建立型模式。

建造者模式一步一步建立一個複雜的物件,它允許使用者只通過指定複雜物件的型別和內容就可以構建它們,使用者不需要知道內部的具體構建細節。

角色

Builder(抽象建造者):它為建立一個產品Product物件的各個部件指定抽象介面,在該介面中一般宣告兩類方法,一類方法是buildPartX(),它們用於建立複雜物件的各個部件;另一類方法是getResult(),它們用於返回複雜物件。Builder既可以是抽象類,也可以是介面。

ConcreteBuilder(具體建造者):它實現了Builder介面,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜物件,也可以提供一個方法返回建立好的複雜產品物件。

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
}

抽象建造者 builder

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

image

建造者模式總結

建造者模式的主要優點如下:

  • 在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。
  • 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,使用者使用不同的具體建造者即可得到不同的產品物件。由於指揮者類針對抽象建造者程式設計,增加新的具體建造者無須修改原有類庫的程式碼,系統擴充套件方便,符合 “開閉原則”。
  • 可以更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不同的方法中,使得建立過程更加清晰,也更方便使用程式來控制建立過程。

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

  • 建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用範圍受到一定的限制。
  • 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和執行成本。

適用場景

  • 需要生成的產品物件有複雜的內部結構,這些產品物件通常包含多個成員屬性。
  • 需要生成的產品物件的屬性相互依賴,需要指定其生成順序。
  • 物件的建立過程獨立於建立該物件的類。在建造者模式中通過引入了指揮者類,將建立過程封裝在指揮者類中,而不在建造者類和客戶類中。
  • 隔離複雜物件的建立和使用,並使得相同的建立過程可以建立不同的產品。

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

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.Serializable, CharSequence {
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    // ...省略...
}

StringBuilder 的父類 AbstractStringBuilder 實現了 Appendable 介面

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    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.Serializable, CharSequence {
    @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<E> extends 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<Integer, String> cache = 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

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


更多內容請訪問我的個人部落格:http://laijianfeng.org/

開啟微信掃一掃,關注【小旋鋒】微信公眾號,及時接收博文推送

關注公眾號及時接收博文推送