Java設計模式14:建造者模式

什麼是建造者模式sql

發現不少框架的源碼使用了建造者模式,看了一下以爲挺實用的,就寫篇文章學習一下,順便分享給你們。數據庫

建造者模式是什麼呢?用一句話歸納就是建造者模式的目的是爲了分離對象的屬性與建立過程,是的,只要記住並理解紅字的幾個部分,建造者模式你就懂了。編程

 

爲何須要建造者模式設計模式

建造者模式是構造方法的一種替代方案,爲何須要建造者模式,咱們能夠想,假設有一個對象裏面有20個屬性:安全

  • 屬性1
  • 屬性2
  • ...
  • 屬性20

對開發者來講這不是瘋了,也就是說我要去使用這個對象,我得去了解每一個屬性的含義,而後在構造函數或者Setter中一個一個去指定。更加複雜的場景是,這些屬性之間是有關聯的,好比屬性1=A,那麼屬性2只能等於B/C/D,這樣對於開發者來講更是增長了學習成本,開源產品這樣的一個對象相信不會有太多開發者去使用。mybatis

爲了解決以上的痛點,建造者模式應運而生,對象中屬性多,可是一般重要的只有幾個,所以建造者模式會讓開發者指定一些比較重要的屬性或者讓開發者指定某幾個對象類型,而後讓建造者去實現複雜的構建對象的過程,這就是對象的屬性與建立分離。這樣對於開發者而言隱藏了複雜的對象構建細節,下降了學習成本,同時提高了代碼的可複用性。框架

雖然感受基本說清楚了,但仍是有點理論,具體往下看一下例子。ide

 

建造者模式代碼示例函數

舉一個實際場景的例子:性能

你們知道一輛車是很複雜的,有發動機、變速器、輪胎、擋風玻璃、雨刮器、氣缸、方向盤等等無數的部件。

用戶買車的時候不可能一個一個去指定我要那種類型的變速器、我要一個多大的輪胎、我須要長寬高多少的車,這是不現實的

一般用戶只會和銷售談我須要什麼什麼樣的類型的車,馬力要不要強勁、空間是否寬敞,這樣銷售就會根據用戶的須要去推薦一款具體的車

這就是一個典型建造者的場景:車是複雜對象,銷售是建造者。我告訴建造者我須要什麼,建造者根據個人需求給我一個具體的對象

根據這個例子,咱們定義一個簡單的汽車對象:

 1 public class Car {
 2 
 3     // 尺寸
 4     private String size;
 5     
 6     // 方向盤
 7     private String steeringWheel;
 8     
 9     // 底座
10     private String pedestal;
11     
12     // 輪胎
13     private String wheel;
14     
15     // 排量
16     private String displacement;
17     
18     // 最大速度
19     private String maxSpeed;
20 
21     public String getSize() {
22         return size;
23     }
24 
25     public void setSize(String size) {
26         this.size = size;
27     }
28 
29     public String getSteeringWheel() {
30         return steeringWheel;
31     }
32 
33     public void setSteeringWheel(String steeringWheel) {
34         this.steeringWheel = steeringWheel;
35     }
36 
37     public String getPedestal() {
38         return pedestal;
39     }
40 
41     public void setPedestal(String pedestal) {
42         this.pedestal = pedestal;
43     }
44 
45     public String getWheel() {
46         return wheel;
47     }
48 
49     public void setWheel(String wheel) {
50         this.wheel = wheel;
51     }
52 
53     public String getDisplacement() {
54         return displacement;
55     }
56 
57     public void setDisplacement(String displacement) {
58         this.displacement = displacement;
59     }
60 
61     public String getMaxSpeed() {
62         return maxSpeed;
63     }
64 
65     public void setMaxSpeed(String maxSpeed) {
66         this.maxSpeed = maxSpeed;
67     }
68 
69     @Override
70     public String toString() {
71         return "Car [size=" + size + ", steeringWheel=" + steeringWheel + ", pedestal=" + pedestal + ", wheel=" + wheel
72             + ", displacement=" + displacement + ", maxSpeed=" + maxSpeed + "]";
73     }
74     
75 }

這裏簡單定義幾個參數,而後建造者對象應運而生:

public class CarBuilder {

    // 車型
    private String type;
    
    // 動力
    private String power;
    
    // 溫馨性
    private String comfort;
    
    public Car build() {
        Assert.assertNotNull(type);
        Assert.assertNotNull(power);
        Assert.assertNotNull(comfort);
        
        return new Car(this);
    }

    public String getType() {
        return type;
    }

    public CarBuilder type(String type) {
        this.type = type;
        return this;
    }

    public String getPower() {
        return power;
    }

    public CarBuilder power(String power) {
        this.power = power;
        return this;
    }

    public String getComfort() {
        return comfort;
    }

    public CarBuilder comfort(String comfort) {
        this.comfort = comfort;
        return this;
    }

    @Override
    public String toString() {
        return "CarBuilder [type=" + type + ", power=" + power + ", comfort=" + comfort + "]";
    }

}

說是建造者,其實也不合適,它只是一箇中間對象,用於接收來自外部的信息,好比須要什麼樣的車型,須要什麼樣的動力啊這些。

而後你們必定注意到了build方法,這個是建造者模式好像約定俗成的方法名,表明建造,裏面把自身對象傳給Car,這個構造方法的實現我在第一段代碼裏面是沒有貼的,這段代碼的實現爲:

public Car(CarBuilder builder) {
    if ("緊湊型車".equals(builder.getType())) {
        this.size = "大小--緊湊型車";
    } else if ("中型車".equals(builder.getType())) {
        this.size = "大小--中型車";
    } else {
        this.size = "大小--其餘";
    }
        
    if ("很溫馨".equals(builder.getComfort())) {
        this.steeringWheel = "方向盤--很溫馨";
        this.pedestal = "底座--很溫馨";
    } else if ("通常溫馨".equals(builder.getComfort())) {
        this.steeringWheel = "方向盤--通常溫馨";
        this.pedestal = "底座--通常溫馨";
    } else {
        this.steeringWheel = "方向盤--其餘";
        this.pedestal = "底座--其餘";
    }
       
    if ("動力強勁".equals(builder.getPower())) {
        this.displacement = "排量--動力強勁";
        this.maxSpeed = "最大速度--動力強勁";
        this.steeringWheel = "輪胎--動力強勁";
    } else if ("動力通常".equals(builder.getPower())) {
        this.displacement = "排量--動力通常";
        this.maxSpeed = "最大速度--動力通常";
        this.steeringWheel = "輪胎--動力通常";
    } else {
        this.displacement = "排量--其餘";
        this.maxSpeed = "最大速度--其餘";
        this.steeringWheel = "輪胎--其餘";
    }
}

這是真實構建對象的地方,不管多複雜的邏輯都在這裏實現而不須要暴露給開發者,仍是那句核心的話:實現了對象的屬性與構建的分離

這樣用起來就很簡單了:

@Test
public void test() {
    Car car = new CarBuilder().comfort("很溫馨").power("動力通常").type("緊湊型車").build();
        
    System.out.println(JSON.toJSONString(car));
}

只須要指定我須要什麼什麼類型的車,而後具體的每一個參數天然根據個人需求列出來了,不須要知道每一個細節,我也能獲得我須要的東西。

 

建造者模式在開源框架中的應用

文章的開頭有說不少開源框架使用了建造者模式,典型的有Guava的Cache、ImmutableMap,不過感受MyBatis更爲你們熟知,且MyBatis內部大量使用了建造者模式,咱們能夠一塊兒來看一下。

以原生的MyBatis(即不使用Spring框架進行整合)爲例,一般使用MyBatis咱們會用如下幾句代碼:

// MyBatis配置文件路徑
String resources = "mybatis_config.xml";
// 獲取一個輸入流
Reader reader = Resources.getResourceAsReader(resources);
// 獲取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 打開一個會話
SqlSession sqlSession = sqlSessionFactory.openSession();
// 具體操做
...

關鍵咱們看就是這個SqlSessionFactoryBuilder,它的源碼核心方法實現爲:

public class SqlSessionFactoryBuilder {

  ...

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  ...

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
    
  ...

}

由於MyBatis內部是很複雜的,核心類Configuration屬性多到爆炸,好比拿數據庫鏈接池來講好了,有POOLED、UNPOOLED、JNDI三種,而後POOLED裏面呢又有各類超時時間、鏈接池數量的設置,這一個一個都要讓開發者去設置那簡直要命了。所以MyBatis在SqlSessionFactory這一層使用了Builder模式,對開發者隱藏了XML文件解析細節,Configuration內部每一個屬性賦值細節,開發者只須要指定一些必要的參數(好比數據庫地址、用戶名密碼之類的),就能夠直接使用MyBatis了,至於可選參數,配置了就拿開發者配置的,沒有配置就默認來一套。

經過這樣一種方式,開發者接入MyBatis的成本被降到了最低,這麼一種編程方式很是值得你們學習,尤爲是本身須要寫一些框架的時候。

一樣的你們能夠看一下Environment,Environment也使用了建造者模式,可是Environment使用建造者模式最大的做用是讓用戶沒法在運行時修改任何環境屬性保證了安全與穩定性,一樣這也是建造者模式的一種經典實現。

 

建造者模式的類關係圖

其實,建造者模式不像一些設計模式有比較固定或者比較相似的實現方式,它的核心只是分離對象屬性與建立,整個實現比較自由,咱們能夠看到我本身寫的造車的例子和SqlSessionFactoryBuilder就明顯不是一種實現方式。

看了一些框架源碼總結起來,建造者模式的實現大體有兩種寫法:

這是一種在Builder裏面直接new對象的方式,MyBatis的SqlSessionFactoryBuilder就是這種寫法,適用於屬性之間關聯很少且大量屬性都有默認值的場景

另一種就是間接new的方式了:

個人代碼示例,還有例如Guava的Cache都是這種寫法,適用於屬性之間有必定關聯性的場景,例如車的長寬高與軸距都屬於車型一類、排量與馬力都與性能相關,能夠把某幾個屬性歸類,而後讓開發者指定大類便可。

整體而言,兩種沒有太大的優劣之分,在合適的場景下選擇合適的寫法就行了。

 

建造者模式的優勢及適用場景

建造者模式這種設計模式,優缺點比較明顯。從優勢來講:

  • 客戶端不比知道產品內部細節,將產品自己與產品建立過程解耦,使得相同的建立過程能夠建立不一樣的產品對象
  • 能夠更加精細地控制產品的建立過程,將複雜對象分門別類抽出不一樣的類別來,使得開發者能夠更加方便地獲得想要的產品

想了想,說缺點,建造者模式說不上缺點,只能說這種設計模式的使用比較受限:

  • 產品屬性之間差別很大且屬性沒有默認值能夠指定,這種狀況是無法使用建造者模式的,咱們能夠試想,一個對象20個屬性,彼此之間毫無關聯且每一個都須要手動指定,那麼很顯然,即便使用了建造者模式也是毫無做用

總的來講,在IT這個行業,複雜的需求、複雜的業務邏輯層出不窮,這必然致使複雜的業務對象的增長,建造者模式是很是有用武之地的。合理分析場景,在合適的場景下使用建造者模式,必定會使得你的代碼漂亮得多。

相關文章
相關標籤/搜索