Java設計模式之builder模式

scu醬油仔發表於2019-01-19

Java設計模式之builder模式

今天學mybatis的時候,知道了SQLSessionFactory使用的是builder模式來生成的。再次整理一下什麼是builder模式以及應用場景。

1. builder簡介

builder模式也叫建造者模式,builder模式的作用將一個複雜物件的構建與他的表示分離,使用者可以一步一步的構建一個比較複雜的物件。

2. 程式碼例項

我們通常構造一個有很多引數的物件時有三種方式:構造器過載,JavaBeans模式和builder模式。通過一個小例子我們來看一下builder模式的優勢。

2.1 構造器過載方式

package com.wangjun.designPattern.builder;

public class Product {
    
    private int id;
    private String name;
    private int type;
    private float price;
    
    public Product() {
        super();
    }
    
    public Product(int id) {
        super();
        this.id = id;
    }

    public Product(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    public Product(int id, String name, int type) {
        super();
        this.id = id;
        this.name = name;
        this.type = type;
    }

    public Product(int id, String name, int type, float price) {
        super();
        this.id = id;
        this.name = name;
        this.type = type;
        this.price = price;
    }

}

使用構造器過載我們需要定義很多構造器,為了應對使用者不同的需求(有些可能只需要id,有些需要id和name,有些只需要name,……),理論上我們需要定義2^4 = 16個構造器,這只是4個引數,如果引數更多的話,那將是指數級增長,肯定是不合理的。要麼你定義一個全部引數的構造器,使用者只能多傳入一些不需要的屬性值來匹配你的構造器。很明顯這種構造器過載的方式對於多屬性的情況是不完美的。

2.2 JavaBeans方式

package com.wangjun.designPattern.builder;

public class Product2 {
    
    private int id;
    private String name;
    private int type;
    private float price;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }

}

JavaBeans方式就是提供setter方法,在使用的時候根據需求先呼叫無參構造器再呼叫setter方法填充屬性值。

Product2 p2 = new Product2();
p2.setId(10);
p2.setName("phone");
p2.setPrice(100);
p2.setType(1);

這種方式彌補了構造器過載的不足,建立例項很容易,程式碼讀起來也很容易。但是,因為構造過程被分到了幾個呼叫中,在構造過程中JavaBeans可能處於不一致的狀態,類無法僅僅通過檢驗構造器引數的有效性來保證一致性。

2.3 builder模式

package com.wangjun.designPattern.builder;

public class Product3 {
    
    private final int id;
    private final String name;
    private final int type;
    private final float price;
    
    private Product3(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.type = builder.type;
        this.price = builder.price;
    }
    
    public static class Builder {
        private int id;
        private String name;
        private int type;
        private float price;
        
        public Builder id(int id) {
            this.id = id;
            return this;
        }
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        public Builder type(int type) {
            this.type = type;
            return this;
        }
        public Builder price(float price) {
            this.price = price;
            return this;
        }
        
        public Product3 build() {
            return new Product3(this);
        }
    }

}

可以看到builder模式將屬性定義為不可變的,然後定義一個內部靜態類Builder來構建屬性,再通過一個只有Builder引數的構造器來生成Product物件。Builder的setter方法返回builder本身,以便可以將屬性連線起來。我們就可以像下面這樣使用了。

Product3 p3 = new Product3.Builder()
                            .id(10)
                            .name("phone")
                            .price(100)
                            .type(1)
                            .build();

當然具體使用builder的情況肯定沒有這麼簡單,但是思路大致一樣:先通過某種方式取得構造物件需要的所有引數,再通過這些引數一次性構建這個物件。比如MyBatis中SqlSessionFactoryBuilder就是通過讀取MyBatis的xml配置檔案來獲取構造SqlSessionFactory所需要的引數的。

相關文章